2021 ICPC 昆明 M Stone Games(可持久化权值线段树)

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),那假设我现在有一个数x被加进来
我可以表示出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);
	}
} 
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值