可持久化字典树—例题

题目

来源:loj6144. 「2017 山东三轮集训 Day6」C

给出一个序列,有如下四种操作:
Xor x:给全部数异或x
And x全部数与上x
Or x
Ask l r k求l~r中的第k小

题目就是想要写一个“可持久化线段树和可持久化字典树”。

其实并不好写。

最后观察性质。对于“&”而言,如果与0,那么这位就全部人统一了。与1没有影响。
“|”类似,“^”也类似。

不过每次这种统一操作之后,字典树的样子就不是我们想要的了。所以需要进行一些改造。我想的还是太复杂了,想着只把更改的部分改了,其他地方保持不动。而别人写的是直接重新建树。

这个地方告诉我,一定要好好的分析时间复杂度。时间复杂度算准了,那么就可以重新建树。毕竟重新建树这样的操作是我们反复写过的。而部分修改是需要重新细节构建的。复杂度允许肯定是重新构树要好。

一开始想题的方向是说会不会&、|、^运算有什么我不熟悉的定律,后来发现没有。

总之这题就是一道暴力的题目。关键是细节要处理好。

可持久化字典树

这个算法的思想还是很简单的。不过我还是有一些细节处理不够好。下面总结一些套路,以便以后实现可持久化字典树的时候,不会被这么多细节所烦恼。

首先啊,建造可持久化数据结构,需要有很多个root,每个root表示的是新“链”。

root[id]表示的内容其实是空的,所以一条长为len的字符串(做个假设),会产生len+1个节点。build的时候从root[id]开始,然后根据s[i]决定走那一个儿子。所以for循环到i的时候,是在处理节点i-1的。就是说i=1时在处理root[id],……i=n时,在处理s[i-1]的情况。所以一定要注意到,最后s[i]的情况是需要单独在build的地下处理的。千万别忘了!!!

query的时候依旧是这样的一个思想,所以呢第一个点访问的点(也就是root[id])没有任何的信息,只是做了一个开头的工作。而最后一个节点很大概率上是不会真正访问的,因为到那里的时候情况就已经完全确定下来了。(比如说这一题,就是已经确定下来这个数是多少了,所以不访问也是OK的。)所以在访问第i个节点时,我们已经处理的内容的内容是i-1这么的,然后现在要处理的是接下来要去哪个地方,往那个地方去会有什么收益。(这样子收益计算次数就是len次,是对的。)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
const int MAXLAY=31;//!!!!!!31

int n,m,a[N][35];
int flg[35];/*flg[i]=-2表示需要一次的反转*/ 
int tot,son[N*35][2],val[N*35];
int root[N];

void insert(int id)
{
	int x=root[id]=++tot,xx=root[id-1];
	for(int i=MAXLAY;i>=1;i--)
	{
		int y=a[id][i];
		if(flg[i]>=0) y=0;
		son[x][y]=++tot;
		son[x][1^y]=son[xx][1^y];
		val[x]=val[xx]+1;
		x=son[x][y];
		xx=son[xx][y];//debug xx=son[x][y];
	}
	val[x]=val[xx]+1;
}

void build()
{
	tot=1;
	memset(son,0,sizeof(son));
	memset(val,0,sizeof(val));
	for(int i=1;i<=n;i++)
	{
		insert(i);
	}
}

int query(int l,int r,int K)
{
	K--;							//debug K should --
	int ret=0;
	l=root[l];r=root[r];
	for(int i=MAXLAY;i>=1;i--)
	{
		if(flg[i]>=0)
		{
			l=son[l][0];
			r=son[r][0];
			if(flg[i]==1) ret+=1<<(i-1);
		}
		else
		{
			int fan=flg[i]==-2?1:0;
			if(K>=val[ son[r][fan^0] ] - val[ son[l][fan^0] ])		//debug //debug K>=val[]-val[]
			{
				K-=val[ son[r][fan^0] ] - val[ son[l][fan^0] ];
				l=son[l][fan^1];
				r=son[r][fan^1];
				ret+=1<<(i-1);
			}
			else
			{
				l=son[l][fan^0];
				r=son[r][fan^0];
			}
		}
	}
	return ret;
}


void getnum(int id,int x)			//debug %10??
{
	for(int i=1;i<=31;i++)
	{
		a[id][i]=x&1;
		x>>=1;
	}
}

char opt[10];
int main()
{
	scanf("%d%d",&n,&m);
	int orsum=0,andsum=(1ll<<30)-1;
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		orsum|=x;
		andsum&=x;
		getnum(i,x);
//		build(i);					//build before flg
	}
//	getnum(0,orsum);
//	getnum(1,andsum);		debug getnum after getnum(1)
	for(int i=1;i<=MAXLAY;i++)
	{
		if((andsum&1) == (orsum&1)) flg[i]=a[0][i];
		else flg[i]=-1;
		andsum>>=1;
		orsum>>=1;
	}
	build();
	while(m--)
	{
		scanf("%s",opt);
		if(opt[0]=='X')//Xor
		{
			int x;
			scanf("%d",&x);
			getnum(0,x);
			for(int i=MAXLAY;i>=1;i--)
			{
				if(a[0][i]==1 && flg[i]>=0) flg[i]^=1;
				else if(a[0][i]==1) flg[i]=-3-flg[i];
			}
		}
		else if(opt[0]=='O')//Or
		{
			int x;
			scanf("%d",&x);
			getnum(0,x);
			bool need=false;
			for(int i=MAXLAY;i>=1;i--)
			{
				if(a[0][i]==1 && flg[i]<=-1)
				{
					flg[i]=1;
					need=true;
				}
				else if(a[0][i]==1 && flg[i]==0)
				{
					flg[i]=1;
				}
			}
			if(need) build();
		}
		else if(opt[1]=='n')//And
		{
			int x;
			scanf("%d",&x);
			getnum(0,x);
			bool need=false;
			for(int i=MAXLAY;i>=1;i--)
			{
				if(a[0][i]==0 && flg[i]<=-1)		//debug flg[i]==-1
				{
					flg[i]=0;
					need=true;
				}
				else if(a[0][i]==0 && flg[i]==1)
				{
					flg[i]=0;
				}
			}
			if(need) build();
		}
		else//Ask
		{
			int l,r,K;
			scanf("%d%d%d",&l,&r,&K);
			int ans=query(l-1,r,K);
			printf("%d\n",ans);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值