bzoj 4408
嗯..
首先考虑如果只有一次询问我们怎么做
设我们当前有个数集{$S$},进行这一询问,我们怎么处理?
首先不妨假设{$S$}单调不降(如果不是这样的话显然排序并不会影响答案)
那么假设前$i$个数都合法,其能组合出的最大的值为$lim$,那么我们只需比较$S_{i+1}$与$lim+1$的大小就可以确定答案是否增大
为什么?
前$i$个数能够表示出$lim$,而如果$S_{i+1}<=lim$,那么一定能表示出$[lim+1,lim+S_{i+1}]$!
为什么?
我们钦定$S_{i+1}$必选,那么我们为了表示出$[lim+1,lim+S_{i+1}]$这些数,我们只需用$1~i$的数表示出$[lim+1-S_{i+1},lim]$即可
考虑到要求$1~i$一定能表示出$[0,lim]$,因此只需要求$lim+1-S_{i+1}\geq 0$即可
也即$S_{i+1}\leq lim+1$
因此如果只有一次询问,我们只需从前向后扫即可
但是现在是多次询问啊!
没有关系,我们考虑上面操作的等价条件:
我们发现,上面操作算出的答案一定满足:将所有小于等于答案的原集合中的数求和等于答案-1!
因此我们只需利用这一性质即可
从1开始枚举一个答案,然后检验区间中小于等于这个答案的数求和是否大于这个答案即可
为了求出区间小于等于某个答案的数的和,需要用可持久化的权值线段树实现
代码很好写
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long #define ls tree[rt].lson #define rs tree[rt].rson using namespace std; const int lim=1000000000; struct Pre_Seg_Tree { int lson,rson; ll sum; }tree[8000005]; ll v[100005]; int rot[100005]; int n,m,tot; void update(int rt) { tree[rt].sum=tree[ls].sum+tree[rs].sum; } void ins(int &rt,int lrt,int l,int r,ll pos) { rt=++tot; if(l==r){tree[rt].sum=tree[lrt].sum+1ll*pos;return;} int mid=(l+r)>>1; if(pos<=mid)rs=tree[lrt].rson,ins(ls,tree[lrt].lson,l,mid,pos); else ls=tree[lrt].lson,ins(rs,tree[lrt].rson,mid+1,r,pos); update(rt); } ll query(int rt1,int rt2,int l,int r,int lq,int rq) { if(l>=lq&&r<=rq)return tree[rt2].sum-tree[rt1].sum; int mid=(l+r)>>1; ll s=0; if(lq<=mid)s+=query(tree[rt1].lson,tree[rt2].lson,l,mid,lq,rq); if(rq>mid)s+=query(tree[rt1].rson,tree[rt2].rson,mid+1,r,lq,rq); return s; } template <typename T> inline void read(T &x) { T f=1,c=0;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){c=c*10+ch-'0';ch=getchar();} x=c*f; } int main() { read(n); for(int i=1;i<=n;i++)read(v[i]),ins(rot[i],rot[i-1],1,lim,v[i]); read(m); while(m--) { int l,r; read(l),read(r); ll ans=1; while(1) { ll sum=query(rot[l-1],rot[r],1,lim,1,ans); if(sum<ans)break; ans=sum+1; } printf("%lld\n",ans); } return 0; }