题意:
给出一个长度为n的数组,有q次询问,每次询问给出一个区间(l,r),求这个区间的数任意相加,不能表示的最小的数是多少,询问采用强制在线。
这里每次询问给出的区间[l,r]还有个小坑点,真正的l,r要结合上一轮查询的结果计算得出,初始。
这里l,r不能顺次求出,也就是不能用已经更新过的l去求r,低级错误却找了好久。
思路:
首先,我们想怎么求一个集合不能表示的最小的数。
最开始我们一个数都不选,那么可表示的数就是0,最小不可表示的数就是1.
那么假设我们当前已经选了若干个数,可以表示的数是(0,x),那对于集合中所有还没选的ax+1,都可以让我当前可表示的数的范围变大,新加入的a和之前选的若干个数可以表示成a,a+1,a+2,...,a+x。即我当前可以表示的最大的数从x变成了x加上所有还没选的小于等于x+1的a。但如果没选的数都大于x+1了呢,这个时候x+1是无论如何也表示不出来的,所以最小不能表示的数就是x+1。
明白了如何求一个集合不能表示的最小的数,我们令pre是上次已经统计过的数的最大值,sum是已经可以表示的数的最大值,当我们一个数都没选时,pre=sum=0,接下来我们只需要利用主席树对某个区间[L,R]不断查寻数值范围在[pre+1,sum+1]内的数就可以,当查询值为0时,说明最大不能表示的数就是sum+1。
样例:
输入
5 5
1 4 2 1 6
1 3
2 1
2 4
1 4
3 4
输出
8
15
4
9
4
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=1e9;
const int N=1e6+10;
const int M=40*N;
int n,q;
int root[N];
int lch[M],rch[M],tot;
ll sum[M];
void cpy(int from,int to){
lch[to]=lch[from];
rch[to]=rch[from];
sum[to]=sum[from];
}
void insert(int &u,int old,int L,int R,ll x){
u=++tot;
cpy(old,u);
sum[u]+=x;
if(L==R)
return ;
int mid=(L+R)>>1;
if(x<=mid)
insert(lch[u],lch[u],L,mid,x);
else
insert(rch[u],rch[u],mid+1,R,x);
}
ll query(int s,int t,int L,int R,int l,int r){
if(L==l&&R==r){
return sum[t]-sum[s];
}
int mid=(L+R)>>1;
if(r<=mid){
return query(lch[s],lch[t],L,mid,l,r);
}
else if(l<=mid){
ll res=0;
res+=query(lch[s],lch[t],L,mid,l,mid);
res+=query(rch[s],rch[t],mid+1,R,mid+1,r);
return res;
}
else
return query(rch[s],rch[t],mid+1,R,l,r);
}
int main(){
scanf("%d%d",&n,&q);
ll tt;
for(int i=1;i<=n;i++){
scanf("%lld",&tt);
insert(root[i],root[i-1],1,inf,tt);
}
ll l,r,lt,rt,ans=0;
while(q--){
scanf("%lld%lld",&l,&r);
l=(l+ans)%n+1;
r=(r+ans)%n+1;
lt=min(l,r),rt=max(l,r);
ll pre=0,sum=0,res;
while(1){
//pre是上次已经统计过的右端点
//sum是当前已经凑出的和[1,sum]都可以凑出
if(pre>inf)
break;
if(sum>=inf)
res=query(root[lt-1],root[rt],1,inf,pre+1,inf);
else
res=query(root[lt-1],root[rt],1,inf,pre+1,sum+1);
if(res==0)
break;
pre=sum+1;
sum+=res;
}
printf("%lld\n",sum+1);
ans=sum+1;
}
}