P4551 最长异或路径 (01字典树,异或前缀和)
Posted kv-stalin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P4551 最长异或路径 (01字典树,异或前缀和)相关的知识,希望对你有一定的参考价值。
题目描述
给定一棵 nn 个点的带权树,结点下标从 11 开始到 NN 。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。
输入输出格式
输入格式:
第一行一个整数 NN ,表示点数。
接下来 n-1n?1 行,给出 u,v,wu,v,w ,分别表示树上的 uu 点和 vv 点有连边,边的权值是 ww 。
输出格式:
一行,一个整数表示答案。
输入输出样例
输入样例#1:
4
1 2 3
2 3 4
2 4 6
输出样例#1:
7
说明
最长异或序列是1-2-3,答案是 7 (=3 ⊕ 4)
数据范围
1≤n≤100000; 0<u,v≤n; 0≤w<2^31
Solution
这道题,套路题啊...
首先要考虑到两个思路:
- 异或的基本性质 :
- 01字典树
1.异或的基本性质:
[{(x+y)}igoplus{y}=x]
2.带修改的01字典树
考虑枚举每一个点对,但是数据范围太大。
此时我们可以使用01字典树,步骤如下:
- 先将边权转化为点权,预先处理出从根节点到每一个点的异或前缀和。
- 然后将所有点的异或前缀和插入字典树中。
- 再进行查询,注意查询时要将查询的点暂时删除。
查询最大值即为答案。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=200008;
int ch[32*maxn][2];
ll val[32*maxn],c[maxn];
int num[32*maxn];
int sz,n;
ll b[maxn];
void insert(ll a)
{
int u=0;
for(int i=32;i>=0;i--)
{
int c=((a>>i)&1);
if(!ch[u][c])
{
memset(ch[sz],0,sizeof(ch[sz]));
val[sz]=0;
num[sz]=0;
ch[u][c]=sz++;
}
u=ch[u][c];
num[u]++;
}
val[u]=a;
}
void update(ll a,int d)
{
int u=0;
for(int i=32;i>=0;i--)
{
int c=((a>>i)&1);
u=ch[u][c];
num[u]+=d;
}
}
ll query(ll x)
{
int u=0;
for(int i=32;i>=0;i--)
{
int c=((x>>i)&1);
if(ch[u][c^1]&&num[ch[u][c^1]])
u=ch[u][c^1];
else u=ch[u][c];
}
return x^val[u];
}
struct sj{
int to;
int next;
int w;
}a[maxn];
int size,head[maxn];
void add(int x,int y,int z)
{
a[++size].to=y;
a[size].w=z;
a[size].next=head[x];
head[x]=size;
}
int v[maxn],now,ans=-1;
void dfs(int x)
{
v[x]=1;
c[x]=now;
for(int i=head[x];i;i=a[i].next)
{
int tt=a[i].to;
if(!v[tt])
{
now^=a[i].w;
dfs(tt);
now^=a[i].w;
}
}
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
dfs(1);
for(int i=1;i<=n;i++)
insert(c[i]);
for(int i=1;i<=n;i++)
{
update(c[i],-1);
int kk=query(c[i]);
ans=max(ans,kk);
update(c[i],1);
}
cout<<ans<<endl;
}
以上是关于P4551 最长异或路径 (01字典树,异或前缀和)的主要内容,如果未能解决你的问题,请参考以下文章