首页 > 代码库 > SHUOJ 1858 分裂的寄生兽

SHUOJ 1858 分裂的寄生兽

 SHUOJ 1858: 分裂的寄生兽

Time Limit: 3 Sec  Memory Limit: 128 MB
Submit: 22  Solved: 7

Description

我们知道小右是一种寄生兽,它的一种特性就是可以进行分裂,但分裂后太小的单位不能存活。有一天,泉新一(你)起床后发现小右分裂出了一排体积为1的N个小寄生兽,这些寄生兽形态不一,只有相同形态的才能合并在一起。已知单个寄生兽的体积需要超过K才能存活,你想知道如果发出指令让[L,R]区间的小寄生兽合并,能够合并出几只可以独立存活的寄生兽,并且这些寄生兽的总体积为多少?

Input

多组数据,第一行是一个整数T,代表有T组数据。(T<=5)

对于每组数据有:

第一行有两个整数N和K,分别代表分裂的寄生兽数量和寄生兽的存活所需体积。

第二行有N个整数a[i],代表这一排寄生兽的形态编号(编号相同才能合并)。(1<=a[i]<=N)

第三行是一个整数Q,代表查询次数。

第四行开始有Q行,每行有整数L, R,代表一次[L,R]区间的查询。

(1<=N,Q<=10^5,  1<=L<=R<=N,  1<=K<=N)

Output

对于每一个查询,输出两个整数,代表[L,R]区间内合并后能够独立存活的寄生兽数量,以及这些能够存活的寄生兽的总体积。

两个整数之间有一个空格,每一次查询都换行。

Sample Input

1 10 3 4 3 2 4 2 8 3 2 4 4 2 3 8 1 10 

Sample Output

1 3 2 7 

HINT

Please use scanf/printf to avoid TLE.

Brute force will also get TLE.

Source

Curs0r

 

这是一道高神(伟大领袖姚哥的室友)出的上海大学研究生算法竞赛防AK题。看完题目以后惊呆了,竟然还有这种神题,感觉最牛的是题目尽然是如此的顺畅,完全没有那种为了出题而出题的感觉,浑然天成,让人感慨大自然的美妙以及高神看的动漫之多。最终打开高神的题解,我不禁感动地热泪盈眶,潸然泪下,在用了三个小时擦眼泪和鼻涕后,才看懂了题解,特此纪念。

高神的题解:

G. 分裂的寄生兽

AK题,

解法有三种:离线树状数组(n*logn)、在线主席树(n*logn)、莫队算法(n*sqrt(n))

时限三秒,三种算法分别跑了0.5s, 1.1s, 1.8s. (没有卡莫队)

前两种的思路是一样的,第二种可以在线维护(但耗内存)

由于题目没有要求强制在线,所以可以用离线树状数组或者莫队。

 

题意是给出一个区间和值K

多次查询[L,R]区间内出现次数大于等于K次的数的种类数, 以及这些数的个数。

 

这题的解题思路来自:SPOJ.  D-Query, 方法大同小异。

 

一、先考虑离线树状数组的做法:

考虑第一问:

现将查询离线,按照查询的左区间分开。

创建一个和原数组大小相同的树状数组,维护前缀和。

从右往左依次扫过数组,

简单地用(几个)数组维护前k个出现的相同元素的位置;

当某个数出现超过k次了,那么就将第前k次出现的位置上树状数组值置1

并且将第前k+1次(如果有的话)的出现位置上减去1

这样的话,当前扫到第L的元素,那么对于查询[L,R],就可以查询对R的前缀和。

 

第二问同理,

用另一个同样的树状数组维护前缀和。

当某个数出现超过k次,则将第前k次出现的位置上置k

同时将第前k+1次出现的位置减去k-1.

同样是离线的查询前缀和。

 

二、这种做法很容易拓展到主席树上,使查询可以支持在线。(不过既然可以离线,那就没有必要)

 

三、莫队算法

其实理应卡莫队,但本来就比较难(麻烦)所以就放宽了时限。

莫队只需要将查询离线,然后分块搞,

每次从[L,R]转移至[L±1,R], 或[L,R±1]

维护好每种数字出现的次数就能轻松求得答案,故一次转移的复杂度是O(1)

所以最终的复杂度就是O(n*sqrt(n))

 

By Curs0r 2014.12.23

//2014/12/24

目前只看懂了树状数组离线处理的做法,所以只能先分享这个。

首先,先解释一下离线处理。离线处理就是先将查询全部保存下来,并且将这些查询按照你的需求排序。从而可以根据排序后的查询来处理,进而达到算法的优化。

第一个查询是输出[L,R]内可以生存的寄生兽个数。所以,离线处理的时候不妨按照R的大小升序排序。这样,我们可以从左至右开始遍历,当遇到一个R的时候,表示一个查询完成,记录下查询的结果。

因为寄生兽必须要满足k个体积才能存活,小于k个时不能存活。所以,在记录的时候,需要记录满足k个时候的寄生兽。因为是从左至右进行的遍历,且排序后R大于等于当前的位置。所以记录满足的寄生兽的时候,只需记录从当前位置开始向左正好满足第k个的位置,给这个位置的值置为1。这样在查询的时候查询区间和就可以得出答案。

通过一个例子来说明

N=10,k=3,依次从左至右的步骤

技术分享

这个理解以后,接下来需要做个优化,在查询[L,R]的时候可以用树状数组来保存这个数组,从而可以在log(n)的复杂度下查询得到[L,R]之间的和。

还要解决的一个问题是需要一个数组来储存下一个这个编号寄生兽的位置。这个可以用一个next数组来实现。

第二个查询是输出[L,R]内可以生存的寄生兽的体积。同理,用树状数组存储的是当前位置开始向左第k个值的位置,给它赋值为k,而对于第k+1个,因为k的位置已经是k了,所以k+1位置赋值为1即可。(具体操作的时候,给k+1个值的位置 -(k-1))。

下面是代码:

  1 #include <iostream>  2 #include <sstream>  3 #include <ios>  4 #include <iomanip>  5 #include <functional>  6 #include <algorithm>  7 #include <vector>  8 #include <string>  9 #include <list> 10 #include <queue> 11 #include <deque> 12 #include <stack> 13 #include <set> 14 #include <map> 15 #include <cstdio> 16 #include <cstdlib> 17 #include <cmath> 18 #include <cstring> 19 #include <climits> 20 #include <cctype> 21 #define INF 0x3f3f3f3f 22 #define MP(X,Y) make_pair(X,Y) 23 #define PB(X) push_back(X) 24 #define REP(X,N) for(int X=0;X<N;X++) 25 #define REP2(X,L,R) for(int X=L;X<=R;X++) 26 #define DEP(X,R,L) for(int X=R;X>=L;X--) 27 #define CLR(A,X) memset(A,X,sizeof(A)) 28 #define IT iterator 29 #define M_PI 3.14159265358979323846 30 #define _ ios_base::sync_with_stdio(0);cin.tie(0); 31 #define X first 32 #define Y second 33 #define MAX_V 10101 34 #define maxn 101010 35 #define lowbit(X) (X & (-X)) 36 using namespace std; 37 typedef long long ll; 38 typedef pair<int,int>PII; 39  40 //保存查询的数据  41 struct Query{ 42     int l,r; 43     int index; 44 }Q[maxn]; 45  46 bool cmp(Query a,Query b){ 47     return a.r<b.r; 48 } 49 //记录查询的答案  50 PII Ans[maxn]; 51 //保存树状数组  52 int dat1[maxn],dat2[maxn]; 53  54 int A[maxn]; 55 void init(){ 56     memset(dat1,0,sizeof(dat1)); 57     memset(dat2,0,sizeof(dat2)); 58 } 59 int n,k; 60 void modify(int *dat,int i,int add){ 61     while(i<=n){ 62         dat[i]+=add; 63         i+=lowbit(i); 64     } 65 } 66 int query(int *dat,int i){ 67     int s=0; 68     while(i>0){ 69         s+=dat[i]; 70         i-=lowbit(i); 71     } 72     return s; 73 } 74 int last[maxn];//计算next数组用到,表示上一次这个值的出现的位置 75 int Next[maxn];//到下一个与当前位置相同的值的位置  76  77 int first[maxn];//第一次出现当前值的位置  78 int cnt[maxn];//当前值出现的次数  79  80 int main() 81 { 82     int T; 83     scanf("%d",&T); 84     while(T--){ 85         init(); 86         scanf("%d%d",&n,&k); 87         for(int i=1;i<=n;++i){ 88             scanf("%d",&A[i]); 89         } 90         int q; 91         scanf("%d",&q); 92         for(int i=1;i<=q;++i){ 93             scanf("%d%d",&Q[i].l,&Q[i].r); 94             Q[i].index=i; 95         } 96         //按照R升序排序  97         sort(Q+1,Q+q+1,cmp); 98          99         //计算next数组 100         memset(last,-1,sizeof(last));101         for(int i=1;i<=n;++i){102             if(last[A[i]]!=-1) Next[last[A[i]]]=i;103             last[A[i]]=i;104         }105         106         memset(cnt,0,sizeof(cnt));107         memset(first,-1,sizeof(first));108         int tt=1;109         for(int i=1;i<=n;++i){110             if(first[A[i]]==-1) first[A[i]]=i;111             if(cnt[A[i]]==k){112                 //查询1 113                 modify(dat1,first[A[i]],-1);114                 //查询2 115                 modify(dat2,first[A[i]],-k+1);116                 first[A[i]]=Next[first[A[i]]];117                 modify(dat1,first[A[i]],1);118                 modify(dat2,first[A[i]],k);119             }else {120                 cnt[A[i]]++;121                 if(cnt[A[i]]==k){122                     modify(dat1,first[A[i]],1);123                     modify(dat2,first[A[i]],k);124                 }125             }126             while(tt<=q && Q[tt].r==i){127                 Ans[Q[tt].index].X=query(dat1,Q[tt].r)-query(dat1,Q[tt].l-1);128                 Ans[Q[tt].index].Y=query(dat2,Q[tt].r)-query(dat2,Q[tt].l-1);129                 tt++;130             }131         }132         for(int i=1;i<=q;++i){133             printf("%d %d\n",Ans[i].X,Ans[i].Y);134         }135     } 136     return 0;137 }

 

SHUOJ 1858 分裂的寄生兽