Bzoj4408--Fjoi2016神秘数
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
对于每个询问,输出一行对应的答案。
题解 :
什么数会不能组成出来,就是在这个区间里所有小于这个数的和小于这个数的时候
那么我们要找的就是第一个小于这个数的所有数的和小于这个数的数
对于一个区间,我们求所有小于等于x的和,设为S,那么由于递推计算这个区间可以组成所有小于等于S的数,然后我们再计算所有小于等于S的数的和,这样迭代计算
当出现小于等于S的数的和等于S时说明S+1就是我们要找的数
具体迭代计算可以用主席树
复杂度证明 :
因为x有可能没在区间中出现过,所以我们设pre(x)为区间中小于x的第一个数
当我们求出所有小于等于x的数的和S,再去求所有小于等于S的数的和T时,迭代到第三步如果没有停止,那么T>2*pre(x)
因为S>=pre(x),而迭代到T若还未停止说明区间中存在pre(x)<ai<=S;那么T相比两步前的x至少扩大2倍
所以迭代次数最多不超过2*log(n)次
总复杂度(nlogn)
代码 :
#include<bits/stdc++.h> #define LL long long #define INF 1000000000 using namespace std; #define MAXN 100005 const int L=0,R=1; int n,m,s[MAXN]; int son[MAXN*33][2],sum[MAXN*33],root[MAXN],sz; void Build(int l,int r,int v,int fr,int &now) { now=++sz; son[now][L]=son[fr][L]; son[now][R]=son[fr][R]; sum[now]=sum[fr]+v; if(l==r) return; int mid=l+r>>1; if(v>mid) Build(mid+1,r,v,son[fr][R],son[now][R]); else Build(l,mid,v,son[fr][L],son[now][L]); } int Qurey(int l,int r,int nl,int nr,int x,int y) { if(l==nl&&r==nr) return sum[y]-sum[x]; int mid=l+r>>1; if(nl>mid) return Qurey(mid+1,r,nl,nr,son[x][R],son[y][R]); else if(nr<=mid) return Qurey(l,mid,nl,nr,son[x][L],son[y][L]); else return Qurey(l,mid,nl,mid,son[x][L],son[y][L])+Qurey(mid+1,r,mid+1,nr,son[x][R],son[y][R]); } int Solve(int l,int r) { int eni=1,t; while((t=Qurey(1,INF,1,eni,root[l-1],root[r])+1)>eni) { eni=t; } return t; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&s[i]);Build(1,INF,s[i],root[i-1],root[i]); } scanf("%d",&m); for(int l,r,i=1;i<=m;i++) { scanf("%d%d",&l,&r); printf("%d\n",Solve(l,r)); } return 0; }