2021 ICPC 昆明 M Stone Games(可持久化权值线段树)
当时场上就是看了一眼,感觉是线段树,不过并不会写,现在来补一发
题目大意:
给你一个1e6长度的序列a(a[i]<=1e9),和q(q<=1e5)次询问,每次询问区间l,r,问从a[l]到a[r]这个子序列所不能表示的最小正整数(表示是指能表示成序列中的一个数或多个数之和),强制在线。
做法
我们先简化问题:一个长度n的序列,如何求他不能表示的最小值呢?
首先可以看到:
区间:1 1 3 20
1.假如区间里没有出现过1,那必然不能表示的最小正整数是1
2.如果1出现了,还有1个1,那我可以用已经有的这个1和这个1组成2(当然1个2也可以)
3.如果它可以组成1~2了,那现在还有一个3,那我们可以用这个3和前面的(0 ~2)表示出3,4,5来
4.现在5已经可以了,我有最后一个一个20,那我们可以搞出0~5+20也就是20 ~25,这时候我们发现6没有出现,那答案是6
我当然不可能这样一直举到1e9去,那么我们来总结下规律:
1.假如我从1到k都可以表示,那么假如我有另一个数m,那我必然可以用这个m和这些1~k组合成m ~m+k的所有数。
2.我现在可以表示出1~k,那么假如说我从1 ~ k+1所有的数的和为sum,那我考虑能不能表示出1 ~sum:
/-------如果sum<=k,说明什么?
首先k+1这个数没有出现
其次前面的数加起来也只有k或更少
那k+1一定表示不出来,答案是k+1
/-------如果sum>k
那按照上面的逻辑,我们可以表示出1~sum的所有值
为啥呢:
我上一步搞出来一个k(k是上一个sum),那假设我现在有一个数m被加进来
我可以表示出x+0~x+k(这个x是小于等于k+1的,所以一定能被它自身或者之前的表示),
我们能表示出1~k,还能从一个小于等于k+1的x表示到x+k
所以我们可以表示到k+x了
把k+x作为新的k
那我再加入一个y,我们就也能表示出y+0~y+k
所以我能表示出k+x+y了
所以,既然我加进来的都小于k
加多少我就能表示到k+多少
因为一共加了sum-k
所以,我们能表示出sum-k+k=sum了
然后把k变成sum,继续重复这一串步骤
所以,我们每次操作需要取出所有区间内1-k的和
那么我们可以使用可持久化权值线段树来解决
具体操作再说吧,回头搞一篇线段树总结
复杂度
应该是q(询问数) * log(线段树查询) * 奇怪的sum增大过程
这个增长应该是斐波那契起步的,应该是log级别吧(反正我也不太会)
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
struct tree{
ll val;
int lc,rc;
};tree t[66000005];
int rt[1000005],a[1000005];
int n,q,cnt=0;
ll maxv=1e9+7;
void build(int l,int r,int pos,int val,int &x,int y){
x=++cnt;
t[x].lc=t[y].lc;
t[x].rc=t[y].rc;
t[x].val=t[y].val+val;
if(l==r) return;
int mid=(l+r)/2;
if(pos<=mid) build(l,mid,pos,val,t[x].lc,t[y].lc);
else build(mid+1,r,pos,val,t[x].rc,t[y].rc);
}
ll query(int l,int r,int ql,int qr,int x,int y){
if(l>=ql&&r<=qr){
return t[y].val-t[x].val;
}
int mid=(l+r)/2;
ll ret=0;
if(ql<=mid) ret+=query(l,mid,ql,qr,t[x].lc,t[y].lc);
if(qr>mid) ret+=query(mid+1,r,ql,qr,t[x].rc,t[y].rc);
return ret;
}
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
build(1,maxv,a[i],a[i],rt[i],rt[i-1]);
}
ll ans=0;
while(q--){
int l,r;
scanf("%d%d",&l,&r);
l=(l+ans)%n+1;
r=(r+ans)%n+1;
if(l>r) swap(l,r);
ll x=0;
while(1){
ll now=query(1,maxv,1,min(x+1,maxv),rt[l-1],rt[r]);
if(now==x) break;
x=now;
}
ans=x+1;
printf("%lld\n",ans);
}
}