Stone Games--可持久化线段树 ICPC昆明

题意:

给出一个长度为n的数组,有q次询问,每次询问给出一个区间(l,r),求这个区间的数任意相加,不能表示的最小的数是多少,询问采用强制在线。

这里每次询问给出的区间[l,r]还有个小坑点,真正的l,r要结合上一轮查询的结果计算得出,初始ans_0=0

这里l,r不能顺次求出,也就是不能用已经更新过的l去求r,低级错误却找了好久。

思路:

首先,我们想怎么求一个集合不能表示的最小的数。

最开始我们一个数都不选,那么可表示的数就是0,最小不可表示的数就是1.

那么假设我们当前已经选了若干个数,可以表示的数是(0,x),那对于集合中所有还没选的a\leqx+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;
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值