bzoj 4408: [Fjoi 2016]神秘数
Submit: 188 Solved: 137
[Submit][Status][Discuss]
Description
一个可重复数字集合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
对于每个询问,输出一行对应的答案。
Sample Input
5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5
Sample Output
2
4
8
8
8
4
8
8
8
HINT
对于100%的数据点,n,m <= 100000,∑a[i] <= 10^9
题解:
先考虑答案是怎么来的,把一个区间单独抽出来,把其中的数字排序,设排序之后是a1,a2,a3,a4,a5,a6,让数字自由组合如果出现中断的地方,则这里就是神秘数。
一句话来说,找到最小的一个神秘数相当于找到一个数x,使得小于x的数字加起来的和小于x。举例来说:1 2 4 9 中数字8保证所有比8小的数字加和:1+2+4=7<8 ,所以8是最小的神秘数。
现在考虑怎么求这个数,由于是区间询问,所以可以想到可持久化线段树,每棵树都维护着1~MAX,然后就可以找到[l,r]里比x小的数的加和啦。
再考虑统计答案,答案有可能是1~sum,枚举答案显然不行。如果现在有1~x+1的数字加起来是y,如果y大于x,也即y大于等于x,说明x不是神秘数
可以让x更新为y,比如1 2 4 8 小于9的数字的加和为15,则1~15里的数字都可以表示出来,这可以由第二段的讲解看出来。然后可以用一个while()来表示了。
1 /************************************************************** 2 Problem: 4408 3 User: __abcdef__ 4 Language: C++ 5 Result: Accepted 6 Time:1948 ms 7 Memory:37212 kb 8 ****************************************************************/ 9 10 #include<iostream> 11 #include<cstdio> 12 #include<cstdlib> 13 #include<cmath> 14 #include<algorithm> 15 #include<cstring> 16 #include<queue> 17 #include<vector> 18 using namespace std; 19 typedef long long LL; 20 const int maxn=100010; 21 int N,M,MAX,SUM,L,R,a[maxn]; 22 struct Tree{ 23 int lc,rc,sum; 24 }T[3000000]; 25 int root[maxn],siz; 26 inline void insert(int &i,int l,int r,int w){ 27 T[++siz]=T[i]; i=siz; T[i].sum+=w; 28 if(l==r) return ; 29 int mid=(l+r)>>1; 30 if(w<=mid) insert(T[i].lc,l,mid,w); 31 else insert(T[i].rc,mid+1,r,w); 32 } 33 inline int query(int x,int y,int l,int r,int ll,int rr){ 34 if(ll<=l&&r<=rr) return T[y].sum-T[x].sum; 35 int mid=(l+r)>>1; 36 int ans=0; 37 if(ll<=mid) ans+=query(T[x].lc,T[y].lc,l,mid,ll,rr); 38 if(mid+1<=rr) ans+=query(T[x].rc,T[y].rc,mid+1,r,ll,rr); 39 return ans; 40 } 41 int main(){ 42 scanf("%d",&N); 43 for(int i=1;i<=N;i++){ 44 scanf("%d",&a[i]); 45 MAX=max(MAX,a[i]); 46 } 47 for(int i=1;i<=N;i++){ 48 root[i]=root[i-1]; 49 insert(root[i],1,MAX,a[i]); 50 } 51 scanf("%d",&M); 52 while(M--){ 53 scanf("%d%d",&L,&R); 54 int nowsum=0,tmpsum=0; 55 while(true){ 56 tmpsum=query(root[L-1],root[R],1,MAX,1,nowsum+1); 57 if(tmpsum>nowsum) nowsum=tmpsum; 58 else break; 59 } 60 printf("%d\n",nowsum+1); 61 } 62 return 0; 63 }