首页 > 代码库 > bzoj4408: [Fjoi 2016]神秘数

bzoj4408: [Fjoi 2016]神秘数

题意:给n个数,定义一段区间神秘数为该区间所有数字通过组合相加所能得到的数的mex,m个询问,对于区间[l,r]询问该区间的神秘树。

如果我们将这段数排序,并且已知前n个数的神秘数为x,即现在凑得的数的区间为[1,x],新加入的数为a,那么不难发现,我们凑得的数又得到了一段区间[a+1,a+x],那么如果a+1<=x,我们就可以拼上这两段,而神秘数变为a+x+1。

也即是说,我们有当前解ans,我们将所有小等ans的数加起来(其实根据前面所推应该是小于,但是写小等不会错,而且对于代码来说更好些,至于为什么不多赘述),如果sigma<ans说明出现了断裂处,即此时ans为答案。否则我们将ans变为sigma+1,继续更新答案。

时间复杂度0(nlogn*P),其中P为常数(当数列为斐波那契时会被卡到极限40)

写代码的时候有一段小插曲。一开始用主席树写的对于每个节点单独累加起来,那样时间复杂度显然不对,实际上直接把每个节点的sum求出来减掉就好了。果然还是太SB啊QAQ

 1 #include<cstdio>
 2 using namespace std;
 3 #define N 100005
 4 int n,m,root[N],ls[100*N],rs[100*N],sum[100*N],cnt,ans,get;
 5 inline int read(){
 6     int x=0,f=1; char a=getchar();
 7     while(a>9 || a<0) {if(a==-) f=-1; a=getchar();}
 8     while(a>=0 && a<=9) x=x*10+a-0,a=getchar();
 9     return x*f;
10 }
11 void inser(int x,int& y,int l,int r,int v){
12     y=++cnt;
13     sum[y]=sum[x]+v;
14     if(l==r) return;
15     ls[y]=ls[x]; rs[y]=rs[x];
16     int mid=(l+r)>>1;
17     if(v>mid) inser(rs[x],rs[y],mid+1,r,v);
18     else inser(ls[x],ls[y],l,mid,v);
19 }
20 int query(int x,int y,int l,int r,int lim){
21     int mid=(l+r)>>1;
22     if(r<=lim) return sum[y]-sum[x];
23     else if(lim<=mid) return query(ls[x],ls[y],l,mid,lim);
24     else return sum[ls[y]]-sum[ls[x]]+query(rs[x],rs[y],mid+1,r,lim);
25 }
26 int main(){
27     n=read();
28     for(int i=1;i<=n;i++) inser(root[i-1],root[i],1,1e9,read());
29     m=read();
30     for(int l,r,i=1;i<=m;i++){
31         l=read(); r=read(); ans=1;
32         while(1){
33             get=query(root[l-1],root[r],1,1e9,ans);
34             if(get<ans) break;
35             ans=get+1;
36         }
37         printf("%d\n",ans);
38     }
39     return 0;
40 }

 

bzoj4408: [Fjoi 2016]神秘数