bzoj 4408: [Fjoi 2016]神秘数 数学 可持久化线段树 主席树
https://www.lydsy.com/JudgeOnline/problem.php?id=4299
一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数。例如S={1,1,1,4,13},
1 = 1
2 = 1+1
3 = 1+1+1
4 = 4
5 = 4+1
6 = 4+1+1
7 = 4+1+1+1
8无法表示为集合S的子集的和,故集合S的神秘数为8。
现给定n个正整数a[1]..a[n],m个询问,每次询问给定一个区间[l,r](l<=r),求由a[l],a[l+1],…,a[r]所构成的可重复数字集合的神秘数。
Input
第一行一个整数n,表示数字个数。
第二行n个整数,从1编号。
第三行一个整数m,表示询问个数。
以下m行,每行一对整数l,r,表示一个询问。
Output
对于每个询问,输出一行对应的答案。
题解
A :
将a从小到大排序,设当前神秘数为ans,扫到了a[i],那么
1. a[i] < ans 时, ans = ans + a[i] ;
2. a[i] > ans时, ans就是最小的神秘数, 跳出循环.
B :
我们也可以发现神秘数简化推法,ans初始为1,那么下一个ans为(sigma (a[i]<=ans) a[i])+1 . (用A部分方法压缩的想法来思考 [ lastans , nowans ) 区间内数的填充), 能够看出sigma的次数是log级的.
此时我们需要维护的是任意区间内的sigma, 主席树可以实现.
这里的主席树并没有离散化,因为主席树只需要建 n*( log总长 ) 个点, 这样写更方便 ( 常数变大了但是并不是很影响复杂度 ), 离散化也阔以, 不过注意一下查找的时候用upper_bound.
(在这里mark一下lower_bound和upper_bound的方向)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 using namespace std; 7 const int maxn=100010; 8 int n,m,cnt=0,tot=0; 9 int a[maxn]={},rt[maxn]={}; 10 int sum[maxn*60]={},lc[maxn*60]={},rc[maxn*60]={}; 11 int read(){ 12 int w=0,f=1;char ch=getchar(); 13 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 14 while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();} 15 return w*f; 16 } 17 void Build(int l,int r,int y,int &x,int v){ 18 x=++tot;sum[x]=sum[y]+v; 19 if(l==r)return; 20 lc[x]=lc[y];rc[x]=rc[y]; 21 int mid=(l+r)/2; 22 if(v<=mid) Build(l,mid,lc[y],lc[x],v); 23 else Build(mid+1,r,rc[y],rc[x],v); 24 } 25 int Query(int l,int r,int x,int y,int v){ 26 if(l==r)return sum[y]-sum[x]; 27 //cout<<sum[y]<<sum[x]<<l<<r<<endl; 28 int mid=(l+r)/2; 29 if(v<=mid) return Query(l,mid,lc[x],lc[y],v); 30 else return Query(mid+1,r,rc[x],rc[y],v)+sum[lc[y]]-sum[lc[x]]; 31 } 32 int main(){ 33 //freopen("a.in","r",stdin); 34 //freopen("a.out","w",stdout); 35 n=read(); 36 for(int i=1;i<=n;i++){a[i]=read();cnt+=a[i];} 37 for(int i=1;i<=n;i++)Build(1,cnt,rt[i-1],rt[i],a[i]); 38 m=read(); 39 for(int i=1;i<=m;i++){ 40 int l=read();int r=read(); 41 int ans=1; 42 for(;;){ 43 int z=Query(1,cnt,rt[l-1],rt[r],ans); 44 //cout<<z<<endl; 45 if(z<ans)break; 46 ans=z+1; 47 } 48 printf("%d\n",ans); 49 } 50 return 0; 51 }