trie树(一)01trie

trie树是个好东西,而它有一类专门的应用,是用来处理区间异或的问题的,考虑到异或操作中我们只关心0/1,因此01tire也就是一种每一个节点最多只有两个儿子的树,具体操作其实跟普通trie树差不多,具体看例题吧

Xor Sum

大意:
给定一个序列,m次询问,每次询问一个值s,求区间中与s异或后结果最大的元素

思路:

对于给定的s,我们考虑它的二进制表示,现在就是要尽可能使得它的每一个非1位都有一个0对应。很明显这个条件不是能肯定满足的,所以我们可以贪心地来处理这件事情。

我们发现,对于一个数的二进制表示,最高位对结果的贡献是大于之前所有位的贡献之和的,所以,如果我们从高到低遍历每一位时,只要当前这一位能满足异或后得到1,我们就一定取这个值。

但是可能有很多的数都满足当前这一位的最优解,而它们的低位不同,自然会影响结果。所以我们考虑以序列中的每一数的01序列建trie树。这样我们的决策就不再是选哪个树了,而是往树的哪一条路往下走。按照贪心的策略,这显然是很好实现的。最后我们只要在每一个序列的末尾标记一下它对应的数字是哪一个,最后输出就好啦

ps:trie树的空间,要开够,这里一般就是数字数*最长01序列长度(=32),而且2^32爆int了,记得开ll

code

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=5e5+10;
ll tr[N*32][3];
ll idx=0;
ll mas[N];
ll as[N*32];
ll n,a,b,c,m;
void insert(ll x)
{
	ll p=0;
	for(int i=31;i>=0;--i)
	{
		bool id=(x>>i)&1;
		if(!tr[p][id]) tr[p][id]=++idx;
		p=tr[p][id];
	}
	as[p]=x;
}
ll find(ll x)
{
	ll p=0;
	for(int i=31;i>=0;--i)
	{
		bool id=((x>>i)&1);
		if(tr[p][!id]) p=tr[p][!id];
		else p=tr[p][id];
	}
	return as[p];
}
signed main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>mas[i];
	for(int i=1;i<=n;++i)
	{
		insert(mas[i]);
	}
	ll ans=0;
	while(m--)
	{
		cin>>a;
		cout<<find(a)<<endl;
	}
	return 0;
}

Chip Factory

大意:
序列里取三个下标不同的数a,b,c,求(a+b)^c的最大值

会发现要求的东西跟上一题差不多,无非就是东西变多了

如果我们还是考虑将所有数都按二进制表示拿去建树的话,那么如何表示(a+b)这种形式就是比较困难,所以我们好像可以考虑n^2遍历将所有的(a+b)形式放进树里,然后再O(n)遍历枚举每一个元素,重复上一题的操作即可。但是这个思路其实还是不对,因为题目要求三个数的下标不能相同。

所以我们可以考虑一个删除操作,也就是将一个数字总=从trie树里去掉。这个也很好实现,给每一个trie树节点标记一个cnt代表有多少个数享有该节点。删除时只要按插入时的顺序遍历一遍树,把沿路的节点cnt--就行了。

现在我们拥有了删除操作,那么就把每一个节点都插进去,遍历到a时把i从树里删掉,再枚举b,从树里删掉b,然后在树里用a+b找最大异或值,最后把a,b插回树里就好了。

另外这道题要求最大值,所以我们在执行find操作时(见上一题代码),再记一个ans来算贡献,具体见代码,很好理解的

另外,空间开够

#include<bits/stdc++.h>
using namespace std;
#define ll int
#define endl '\n'
const ll N=1e3+10;
ll tr[N*35][3];
ll idx=0;
ll mas[N];
ll as[N*35];
ll exi[N*35];//是否存在 
ll n,a,b,c,m;
inline void insert(ll x,ll fl)
{
	ll p=0;
	for(int i=30;i>=0;--i)
	{
		bool id=(x>>i)&1;
		if(!tr[p][id]) tr[p][id]=++idx;
		p=tr[p][id];
		exi[p]+=fl;
	}
	as[p]=x;
}
inline ll find(ll x)
{
	ll p=0;
	for(int i=30;i>=0;--i)
	{
		bool id=((x>>i)&1);
		if(exi[tr[p][!id]]>0) p=tr[p][!id];
		else p=tr[p][id];
	}
	return as[p];
}
signed main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>mas[i];
	for(int i=1;i<=n;++i) insert(mas[i],1);
	ll ans=0;
	for(int i=1;i<=n;++i)
	{
		insert(mas[i],-1);//删除
		for(int j=1;j<=n;++j)
		{
			if(i==j) continue;
			insert(mas[j],-1);
			ans=max(ans,find(mas[i]+mas[j])^(mas[i]+mas[j]));
			insert(mas[j],1);
		}
		insert(mas[i],1);
	}
	cout<<ans<<endl;
	return 0;
}

学会上面那些你就能在洛谷水一道紫题了~ 

Nikitosh and xor

大意:

求两个不相交区间各自的异或和之和的最大值

思路:
看到区间操作不难想到前缀和,又想到异或操作的 “两次等于没有” 性质(取名小能手),这就更加确定了我们的想法。

要求两个区间的异或和之和的最大值,不难想到dp

l【i】代表i点左边的某个序列的最大异或和,r【i】同理

那么答案就是max(l[i]+r[i+1]),1<=i<=n;

接下来考虑如何求单个区间的最大异或和 

不妨对原序列做一个前缀和

异或操作满足差分的可行性( “两次等于没有” 性质),所以我们在前面的前缀和序列里取值,拿当前的前缀和与其异或,算出来就是一个区间的异或和。

所以对于当前枚举到的异或和,如果我们已经将它之前的所有前缀和都插进树里的话,就是等价于第一题了(在序列里取一个数使得异或值最大)

考虑到这里已经差不多了,但还是有点瑕疵,就是有可能一个数本身就是最大的区间异或和,所以我们得在树里插入一个0,使得每一个数有可能保留其自身

code

#include<bits/stdc++.h>
using namespace std;
#define ll int
#define endl '\n'
const ll N=4e5+10;
ll tr[N<<5][3];
ll idx=0;
ll n,a,b,c,m;
ll l[N],r[N];
ll sum[N];
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}

void insert(ll x)
{
	ll p=0;
	for(int i=31;i>=0;--i)
	{
		bool id=(x>>i)&1;
		if(!tr[p][id]) tr[p][id]=++idx;
		p=tr[p][id];
	}
}
ll find(ll x)
{
	ll p=0;
	ll ans=0;
	for(int i=31;i>=0;--i)
	{
		bool id=((x>>i)&1);
		if(tr[p][!id]) p=tr[p][!id],ans+=pow(2,i);
		else p=tr[p][id];
	}
	return ans;
}
signed main()
{//ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	n=read();
	for(int i=1;i<=n;++i) a=read(),sum[i]=sum[i-1]^a;
	insert(0);//attetion! 
	for(int i=1;i<=n;++i)
	{
		insert(sum[i]);
		l[i]=max(l[i-1],find(sum[i]));
	}
	for(int i=0;i<=idx;++i) for(int j=0;j<=1;++j) tr[i][j]=0;
	idx=0;
	for(int i=n;i;--i)
	{
		insert(sum[i]);
		r[i]=max(r[i+1],find(sum[i]));
	}
	ll ans=0;
	for(int i=1;i<=n;++i)
	{
		ans=max(ans,l[i]+r[i+1]);
	}
	printf("%d",ans);
	return 0;
}

最长异或路径

大意:
求树上一条路径,其区间异或和最大

思路:
那么这个就是上一题的一个弱化版本了,因为我们只要求一个区间就行了

另外,树上路径跟区间前缀和没啥区别,因为我们有“两次等于没有” 性质!只要把每一个节点到1的路径长度放进数里,两点a,b的路径异或和就可以考虑成a到根的路径异或和再异或上根到b的路径异或和,中间如果有重复,它们自己会消掉的~

就没了

code

#include<bits/stdc++.h>
using namespace std;
#define ll int
#define endl '\n'
const ll N=1e5+10;
ll tr[3000010][11];
ll idx=0;
ll n,a,b,c,m;
ll dis[N];
struct ty{
	ll t,l,next;
}edge[N<<1];
ll cn=0;
ll head[N];
void add(ll a,ll b,ll c)
{
	edge[++cn].t=b;
	edge[cn].l=c;
	edge[cn].next=head[a];
	head[a]=cn; 
}
void dfs(ll id,ll p)
{
	for(int i=head[id];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(y==p) continue;
		dis[y]=dis[id]^edge[i].l;
		dfs(y,id);
	}
}
void insert(ll x)
{
	ll p=0;
	for(int i=(1<<30);i;i>>=1)
	{
		bool id=x&i;
		if(!tr[p][id]) tr[p][id]=++idx;
		p=tr[p][id];
	}
}
ll find(ll x)
{
	ll p=0;
	ll sum=0;
	for(int i=(1<<30);i;i>>=1)
	{
		bool id=x&i;
		id=!id;
		if(tr[p][id]) p=tr[p][id],sum+=i;
		else p=tr[p][!-id];
	}
	return sum;
}
int main()
{ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	memset(head,-1,sizeof head);
	cin>>n;
	//for(int i=1;i<=n;++i) cin>>mas[i];
	for(int i=1;i<n;++i)
	{
		cin>>a>>b>>c;
		add(a,b,c);
		add(b,a,c);
	}
	dfs(1,-1);
	for(int i=1;i<=n;++i)
	{
		insert(dis[i]);
	}
	ll ans=0;
	for(int i=1;i<=n;++i)
	{
		ans=max(ans,find(dis[i]));
	}
	cout<<ans;
	return 0;
}

01tire就是这样,贪心+二进制,后面也许会更新

也可能会加入新的trie树的应用 

未完待续...

  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值