【POJ 3764】The xor-longest Path 中文题意&题解&代码(C++)

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);
    }
}

你可能感兴趣的:(异或,树,poj,字典树)