正题
题目链接:https://www.luogu.com.cn/problem/P4587
题目大意
nnn个数,每次选择一个区间,然后询问这个区间的子集和所不能表示的最小的正整数。
解题思路
假设我们从小到大加入数字,我们发现如果这个数不是111显然这个区间内至少有一个111。
我们假设现在能表示的最小的数是mxmxmx,加入一个数xxx时,如果x>mx+1x> mx+1x>mx+1那么mx+1mx+1mx+1就不能表现出来,否则我们就可以表示出1∼mx+x1\sim mx+x1∼mx+x的数,即让mx=mx+xmx=mx+xmx=mx+x。
考虑如何优化,如果我们之前能表现出1∼mx1\sim mx1∼mx,加上某些数之后能表现出1∼mx′1\sim mx'1∼mx′了,那么我们显然可以寻找任何一个在mx+1∼mx′+1mx+1\sim mx'+1mx+1∼mx′+1里的数字来扩充,那么我们之间将这段区间的和取来不就好了吗。这个可以用主席树维护。
时间复杂度如何,我们发现每次找到的区间和如果还能往下显然是大于mxmxmx的,然后会让mx′+mxmx'+mxmx′+mx也就是每次至少可以让mx′mx'mx′加上自己的一半,这个时间复杂度是接近倍增也就是logloglog的。
所以时间复杂度O(nlog2n)O(n\log^2 n)O(nlog2n)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=1e6+10,M=5e6+10,inf=1e9;
ll n,m,cnt,rt[N];
ll ls[M],rs[M],sum[M];
ll Change(ll x,ll L,ll R,ll val){ll y=++cnt;rs[y]=rs[x];ls[y]=ls[x];ll mid=(L+R)>>1;sum[y]=sum[x]+val;if(L==R)return y;if(val<=mid)ls[y]=Change(ls[x],L,mid,val);else if(val>mid)rs[y]=Change(rs[x],mid+1,R,val);return y;
}
ll Ask(ll x,ll y,ll L,ll R,ll l,ll r){if(!(sum[x]-sum[y]))return 0;if(l==L&&r==R)return sum[x]-sum[y];ll mid=(L+R)>>1;if(r<=mid)return Ask(ls[x],ls[y],L,mid,l,r);if(l>mid)return Ask(rs[x],rs[y],mid+1,R,l,r);return Ask(ls[x],ls[y],L,mid,l,mid)+Ask(rs[x],rs[y],mid+1,R,mid+1,r);
}
int main()
{scanf("%lld",&n);for(ll i=1;i<=n;i++){ll x;scanf("%lld",&x);rt[i]=Change(rt[i-1],1,inf,x);}scanf("%lld",&m);for(ll i=1;i<=m;i++){ll l,r,lim=0,at=0;scanf("%lld%lld",&l,&r);while(1){ll w=Ask(rt[r],rt[l-1],1,inf,lim+1,at+1);if(!w)break;lim=at+1;at+=w;}printf("%lld\n",at+1);}return 0;
}