LOJ6100-主席树区间修改和查询,二进制,二分

1 篇文章 0 订阅
1 篇文章 0 订阅

LOJ6100

题目描述

题目描述

题解

该题大致思路便是:
第一步 求出每一个点最远能保持单调不下降到哪个点(记为 n x t [ i ] nxt[i] nxt[i]
第二步 主席树区间修改 b u i l d ( r t [ i − 1 ] , r t [ i ] , 1 , n , i , n x t [ i ] ) build(rt[i-1],rt[i],1,n,i,nxt[i]) build(rt[i1],rt[i],1,n,i,nxt[i]),区间查询 b u i l d ( r t [ l − 1 ] , r t [ r ] , 1 , n , l , r ] ) build(rt[l-1],rt[r],1,n,l,r]) build(rt[l1],rt[r],1,n,l,r])
第二步其实比较好实现,主要是第一步。
首先我考虑的是直接二分,但显然是不满足二分单调性的,那么我们尝试做下列转化
我们用 f [ 0 ] [ i ] [ j ] f[0][i][j] f[0][i][j]表示前 i i i个数连续异或的过程中,以第 j j j位为最高位时,从 0 0 0变成 1 1 1的次数。
f [ 1 ] [ i ] [ j ] f[1][i][j] f[1][i][j]同理,求的方法参考下方代码(能进一步对二进制操作的熟悉)
然后再考虑二分求出对位置 x x x n x t [ x ] nxt[x] nxt[x],假设当前二分值为 y y y
如果 s u m [ x − 1 ] sum[x-1] sum[x1] s u m [ i ] sum[i] sum[i]表示前缀异或和)当前枚举的最高位第 j j j位为 1 1 1,而且 f [ 0 ] [ y ] [ j ] − f [ 0 ] [ x − 1 ] [ j ] > 0 f[0][y][j]-f[0][x-1][j]>0 f[0][y][j]f[0][x1][j]>0 那么说明在 x x x y y y之间,发生过 0 − > 1 − > 0 0->1->0 0>1>0的过程即使得异或值减小,故当前位置不合法,当 s u m [ x − 1 ] sum[x-1] sum[x1]当前枚举的最高位第j位为0时同理,显然这是满足二分单调性的。
之所以得出以上方法,我们有以下依据:
1,一个数对前面异或和的影响只和该数的最高位与前面异或值的当前位的关系有关。
因为一个数的最高位一定为 1 1 1,那么如果前面那个数的当前位也是 1 1 1,那么异或上这个数的话,前面的异或和一定会减小,反之会增大

代码

#include<bits/stdc++.h>
#define int long long
#define M 200009
using namespace std;
int read(){
	int f=1,re=0;
	char ch;
	for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
	if(ch=='-'){f=-1,ch=getchar();}
	for(;isdigit(ch);ch=getchar()) re=(re<<3)+(re<<1)+ch-'0';
	return re*f;
}
struct tree{
	int l,r,sum,add;
}tr[M*32];
int n,q,f[2][M][36],sum[M],cnt,rt[M],lastans;
bool bit(int x,int i){return (x>>i)&1;}
int gethigh(int x){
	for(int i=33;i>=0;i--)
		if(bit(x,i)) return i;
	return -1;
}
bool check(int x,int y){
	for(int i=33;i>=0;i--){
		if(bit(sum[x-1],i)&&f[0][y][i]-f[0][x-1][i]>0) return false;
		if(!bit(sum[x-1],i)&&f[1][y][i]-f[1][x-1][i]>0) return false;
	}return true;
}
int getnxt(int pos){
	int l=pos,r=n;
	while(l<r){
		int mid=(l+r+1)>>1;
		if(check(pos,mid)) l=mid;
		else r=mid-1;
	}return l;
}
void build(int x,int &y,int l,int r,int ql,int qr){
	y=++cnt,tr[y]=tr[x];
	if(l==ql&&r==qr){
		tr[y].add++;
		return;
	}tr[y].sum+=(qr-ql+1);
	int mid=(l+r)>>1;
	if(qr<=mid) build(tr[x].l,tr[y].l,l,mid,ql,qr);
	else if(ql>mid) build(tr[x].r,tr[y].r,mid+1,r,ql,qr);
	else build(tr[x].l,tr[y].l,l,mid,ql,mid),build(tr[x].r,tr[y].r,mid+1,r,mid+1,qr);
}
int query(int x,int y,int l,int r,int ql,int qr){
	int tmp=(tr[y].add-tr[x].add)*(qr-ql+1);
	if(l==ql&&r==qr) return tmp+tr[y].sum-tr[x].sum;
	int mid=(l+r)>>1;
	if(qr<=mid) return tmp+query(tr[x].l,tr[y].l,l,mid,ql,qr);
	else if(ql>mid) return tmp+query(tr[x].r,tr[y].r,mid+1,r,ql,qr);
	else return tmp+query(tr[x].l,tr[y].l,l,mid,ql,mid)+query(tr[x].r,tr[y].r,mid+1,r,mid+1,qr);
}
signed main(){
	n=read();
	for(int i=1;i<=n;i++){
		int x=read();
		sum[i]=sum[i-1]^x;
		for(int j=0;j<=33;j++) f[0][i][j]=f[0][i-1][j],f[1][i][j]=f[1][i-1][j]; 
		int h=gethigh(x);
		if(h!=-1){
			if(bit(sum[i-1],h)) f[1][i][h]++;
			else f[0][i][h]++;
		}
	}for(int i=1;i<=n;i++) build(rt[i-1],rt[i],1,n,i,getnxt(i));
	q=read();
	for(int i=1;i<=q;i++){
		int l=(read()+lastans)%n+1,r=(read()+lastans)%n+1;
		if(l>r) swap(l,r);
		printf("%lld\n",lastans=query(rt[l-1],rt[r],1,n,l,r));
		lastans%=n;
	}return 0;
} 

启发

1,对二进制的用法有了一定的理解
2,对主席树的可持久化有了更深的理解,主席树更像是记录下每一个操作,因此这里操作可以包括区间操作(但貌似只能用lazy tag)和单点操作,查询也是同样的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值