BZOJ4408: [Fjoi 2016]神秘数
$n \leq 100000$个正整数,$m \leq 100000$个询问,每次问区间$[L,R]$不在这几个数组成的可重集合的子集数字和的集合的最小正整数,说人话就是这几个数瞎选加起来不能凑成的最小正整数。
以为是数学题。。没想到是一主席树。。
可以考察集合里加入一个数之后对集合不能凑成的数的影响。假设原先集合最小不能凑成的数为$x$,加入了一个$y$,如果$y>x$那没意义,如果$y<=x$,相当于把一个背包移动$y$位后和原来的或起来,那么1到$x+y-1$都可以取到,答案至少应该是$x+y$(可能原来就能取到这个数,所以答案会更大但不会更小);再判断是不是答案时,需要考察$\leq x+y$的所有数字。
由于$x->x+y$是由“$x$是原来的答案,且$ \leq x$的数的和超过了$x$”,那可不可以得出这样的结论:$x$从1开始验证,原答案是$x$时,$\leq x$的数的和$sum$超过了$x$,那么$1$到$sum$的数字都是能得到的!
证明:当$x=1$时显然。当$x>1$时,假设上一个验证的答案是$y$。如果比$x$小的数加起来补到$x$那$x$就得不到;如果加起来超过$x$,由于$\leq y$的数加起来到了$x-1$,因此一定有一些数字$>y$。把能得到的数放在一个背包里,一个$>y$而$\leq x$的数$a$会把这个背包移动$a$位然后或起来,由于这个数$\leq x$,而原来背包$1$到$x-1$都是已经可达的,因此这样的一次移动+取或不会在$1$到$x+z-1$之间造成空隙,因此每个数在背包里移动+取或后,能取到$1$到$x-1+这些>y而\leq x$的数且不留空隙。
那复杂度呢?最坏的情况下,$y->x$之后每次增添一个数$y+1$,这样可构造数列$1,2,3,5,8,13……$这数列有点熟??所以复杂度是约等于$logMax$的,每次需要对一个区间找小于等于某个数的数的和,上主席树。复杂度$m*log_2n*log_2Max$。
1 //#include<iostream> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cstdio> 5 //#include<time.h> 6 //#include<complex> 7 #include<algorithm> 8 #include<stdlib.h> 9 using namespace std; 10 11 int n,m; 12 #define maxn 100011 13 int root[maxn]; 14 int a[maxn],lisa[maxn],li=0; 15 struct SMT 16 { 17 struct Node 18 { 19 int ls,rs; 20 int sum; 21 }a[maxn*20]; 22 int size,n; 23 void clear(int m) {size=0; a[0].sum=0; n=m;} 24 void insert(int &x,int y,int L,int R,int pos) 25 { 26 x=++size; a[x].sum=a[y].sum+lisa[pos]; 27 if (L==R) {a[x].ls=a[x].rs=0; return;} 28 int mid=(L+R)>>1; 29 if (pos<=mid) insert(a[x].ls,a[y].ls,L,mid,pos),a[x].rs=a[y].rs; 30 else insert(a[x].rs,a[y].rs,mid+1,R,pos),a[x].ls=a[y].ls; 31 } 32 void insert(int &x,int y,int pos) {insert(x,y,1,n,pos);} 33 int query(int x,int y,int v) 34 { 35 v=upper_bound(lisa+1,lisa+1+li,v)-lisa; 36 if (v==li+1) return a[y].sum-a[x].sum; 37 int ans=0,L=1,R=n; 38 while (L<R) 39 { 40 int mid=(L+R)>>1; 41 if (v<=mid) x=a[x].ls,y=a[y].ls,R=mid; 42 else ans+=a[a[y].ls].sum-a[a[x].ls].sum,x=a[x].rs,y=a[y].rs,L=mid+1; 43 } 44 return ans; 45 } 46 }t; 47 48 int main() 49 { 50 scanf("%d",&n); 51 for (int i=1;i<=n;i++) scanf("%d",&a[i]),lisa[++li]=a[i]; 52 sort(lisa+1,lisa+1+n); li=unique(lisa+1,lisa+1+li)-lisa-1; t.clear(li); 53 for (int i=1;i<=n;i++) a[i]=lower_bound(lisa+1,lisa+1+li,a[i])-lisa,t.insert(root[i],root[i-1],a[i]); 54 scanf("%d",&m); 55 while (m--) 56 { 57 int x,y; scanf("%d%d",&x,&y); 58 int ans=1,tmp; 59 while ((tmp=t.query(root[x-1],root[y],ans))>=ans) ans=tmp+1; 60 printf("%d\n",ans); 61 } 62 return 0; 63 }