The xor-longest Path
Description
In an edge-weighted tree, the xor-length of a path p is defined as the xor sum of the weights of edges on p:
⊕ is the xor operator.
We say a path the xor-longest path if it has the largest xor-length. Given an edge-weighted tree with n nodes, can you find the xor-longest path?
Input
The input contains several test cases. The first line of each test case contains an integer n(1<=n<=100000), The following n-1 lines each contains three integers u(0 <= u < n),v(0 <= v < n),w(0 <= w < 2^31), which means there is an edge between node u and v of length w.
Output
For each test case output the xor-length of the xor-longest path.
Sample Input
4
0 1 3
1 2 4
1 3 6
Sample Output
7
Hint
The xor-longest path is 0->1->2, which has length 7 (=3 ⊕ 4)
中文题意:
给出n个结点,n-1条边,每条边附有一个权值. 定义:两结点间的异或长度为两点之间所有边权值相异或得到的值.求:这个图中的最长异或长度.
题解:
首先我们需要知道异或的两个性质
A xor B = B xor A , A xor B xor B = A xor 0 = A
其次我们发现这是一个有n个点,n-1条边的连通图,即是一棵树,那么任意两点间的路径就一定只有一条。
这样的话我们很容易会想到一个O(n^2)的做法,那就是首先以任意一个节点作为根节点(为了方便我们取0节点为根节点)写一个DFS遍历整棵树,就能求出每个点到根节点的路径的异或长度记为 val [ i ] ,那么利用上述异或的第二个性质就能理解,任意两点(如x,y)间的路径的异或长度 L(x,y) = val [ x ] xor val [ y ](不理解的画个图自己试一试就知道了,然后直接两重for循环来枚举点求其中异或的最大值就可以了,但这样做对于题上的数据范围显然是超时的,因此我们需要优化一下这个算法。
既然超时是因为枚举两个点,那么我们可不可以只枚举一遍呢?
想到两个数间异或的运算是两个数转成二进制后的每一位相互比较,相同为0,不同为1,既然要求最长路径,那我们就可以每次贪心的每一位都去取和自己不同数,如果找不到那就只好取相同的数,(即假如 x 的第 i 位是 0 那么他最优选择就是第 i 位为 1 的数,如果找不到第 i 位为1的就只能找第 i 位为 0 的)优先考虑最高位,再到最低位,因为二进制数 1000 是肯定大于 0XXX 的,这样的话就问题就转到了怎么在一堆序列中找到一个特定的序列,脑洞开一下(当然是别的聚聚的脑洞大开,本人只是一个蒟蒻)就想到了用字典树来解决问题,因为最大只有2^31,那么就开一个有31层的字典树,每层有0和1两个儿子,字典树从上往下存高位到低位,一遍for循环,每次先在字典树里搜一下最优的序列,异或后 和现有的最优答案取max,再将自己所代表的序列插入字典树,复杂度差不多是O(n*31),就可以过这道题了。
提醒一下,这道题据做过的大神说用C++算法库里的vector会超时,因此需要手写邻接表,相当的麻烦,不会写邻接表的请移步我的另一篇博客不用vector的邻接表的写法,介绍的还算详细ovo
字典树不会的到网上找大神们的博客自学吧
最后再提醒一下数组一定要开够,poj上有时候把RE报成WA,这坑爹的设定坑了我快半个小时,还是细心点好orz
代码:
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
#define MAXN 112222
#define MAXM 3122222 //QAQ!!!!
using namespace std;
int tot,s,head[MAXN];
struct node1{
int v,w,next;
}edge[MAXN*2];//因为双向边,所以*2
struct node2{
int ch[3];
}tr[MAXM];//字典树
void addedge(int ui,int vi,int wi)//邻接表加边
{
edge[tot].v=vi;
edge[tot].w=wi;
edge[tot].next=head[ui];
head[ui]=tot++;
edge[tot].v=ui;
edge[tot].w=wi;
edge[tot].next=head[vi];
head[vi]=tot++;
}
int n,ans,flag[MAXN],val[MAXN];
void dfs(int x,int l)//dfs求出val[i]
{
val[x]=l;
flag[x]=1;
for (int i=head[x];i!=0;i=edge[i].next)
{
int vv=edge[i].v;
int ww=edge[i].w;
if (flag[vv]!=1)
dfs(vv,l^ww);
}
}
void newnode(int x,int tmp)
//在字典树上开新点,这是种比较麻烦的写法,还有一种更加简捷的方法自行查询
{
s++;
tr[x].ch[tmp]=s;
tr[s].ch[0]=0;
tr[s].ch[1]=0;
}
void add(int x)//在字典树上加值
{
int now=0;
for (int i=30;i>=0;i--)
{
int p;
if ((1<<i)&x) p=1;
else p=0;
if (!tr[now].ch[p]) newnode(now,p);
now=tr[now].ch[p];
}
}
int solve(int x)//贪心的找最优序列
{
int now=0,tmp=0;
for (int i=30;i>=0;i--)
{
int p;
if ((1<<i)&x) p=0;
else p=1;
if (tr[now].ch[p]) tmp+=(1<<i);
else p^=1;
now=tr[now].ch[p];
}
return tmp;
}
int main()
{
while( (scanf("%d",&n))!=EOF )
{
tot=1,ans=0,s=0;
tr[s].ch[0]=0;
tr[s].ch[1]=0;
memset(head,0,sizeof(head));
memset(flag,0,sizeof(flag));
//多组数据记得清零
for (int i=1;i<=n-1;i++)
{
int ui,vi,wi;
scanf("%d%d%d",&ui,&vi,&wi);
addedge(ui,vi,wi);
}
dfs(0,0);
for (int i=0;i<n;i++)
{
ans=max(ans,solve(val[i]));
add(val[i]);
}
printf("%d\n",ans);
}
}