trie树的应用:最长异或值路径

题目链接:https://www.acwing.com/problem/content/146/

题目:

给定一个树,树上的边都具有权值。

树中一条路径的异或长度被定义为路径上所有边的权值的异或和:

formula.png

⊕为异或符号。

给定上述的具有 n 个节点的树,你能找到异或长度最大的路径吗?

输入格式

第一行包含整数 n,表示树的节点数目。

接下来 n−1 行,每行包括三个整数 u,v,w,表示节点 u 和节点 v 之间有一条边权重为 w。

输出格式

输出一个整数,表示异或长度最大的路径的最大异或和。

数据范围

1≤n≤100000,
0≤u,v<n,
0≤w<2^31

输入样例:

4
0 1 3
1 2 4
1 3 6

输出样例:

7

样例解释

样例中最长异或值路径应为 0->1->2,值为 7(=3⊕4)

思路:

树型题目的一个常用技巧就是:求某个树型结构的两个点之间的路径关系,时常会被转化为点A到根节点的路径 和 点B到根节点的路径之间的关系。 

 所以这道题,我么可以先建好树型结构,然后随便选定一个节点作为根节点,然后对这个树型结构的所有节点进行dfs,求所有节点到根节点的路径的异或和存入到数组a[]中。

 于是数组a[]中存的就是各个点到根节点的路径的异或和,而此题求的是任意两点A,B之间路径的异或和最大值, 所以就可以使用trie树,从a[]中找到异或和的最大值。

 通过trie树找到所有值中异或值的最大值:

如给出一组数,3,5,4,6 从中选出两个数求异或的最大值。 

暴力的做法是 

for(int i = 0 ;  ; i++)

{

        for(int j = 0 ; j < i ; j++)

        {

        }

}

但可以使用trie树对第二层for循环进行优化,如取值范围为0~ 2 ^31次方,就可以将第二层for优化到固定31次,使得时间复杂度变为31 * n.

思路:

求3(000....0011)的异或最大值,那么异或的最大情况肯定是,首先第30位(从左往右 位数分别为:30,29,....,0)的值为1时的x,x 与 3的异或可以使得所求值的最高位为1.

同样,第29位 也寻找与 3的第29位不同的情况。

但是如果trie树存储的时候没有这种情况的边,由于2进制表示每一位的值要么为0,要么为1.

所以没有异或最大的情况,就只能往唯一条边方向往下,最终得到的值就是 与3异或最大的值。

举例: 

 现在已经通过trie树存储3和5的情况(以3位为例),现在找6(110)的最大异或值,

第2位为1,所以找0,于是向左走,第1位为1,所以想要找0,但是当前不存在0的路径,所以往1的方向走,第0位为0,所以找1,于是往1的方向走,所以在3,5,6的情况下,与6异或最大的就是3.

求一组数的最大异或值 参考题目链接:https://www.acwing.com/problem/content/145/ 

 代码实现:

# include <iostream>
# include <cstring>
using namespace std;

const int N = 100010;

int a[N];  // 存的为每个节点到根节点的总路径的异或值

int h[N],e[2 * N],ne[2 * N],w[2 * N],idx;

int p[31 * N + 10][2],cnt;


int n;

void add(int l, int r , int wei)
{
    e[idx] = r;
    w[idx] = wei;
    ne[idx] = h[l];
    h[l] = idx++;
}

void dfs(int i , int father , int num)
{
    a[i] = num;
    for(int j = h[i] ; j != -1 ; j = ne[j])
    {
        int k = e[j]; //真实的节点
        if(k != father)
        {
            dfs(k,i,num ^ w[j]);
        }
    }
}

void insert(int x)
{
    int temp = 0;
    for(int i = 30 ; i >= 0 ; i--)
    {
        int u = (x >> i) & 1;
        if(!p[temp][u])
        {
            p[temp][u] = ++cnt;
        }
        temp = p[temp][u];
    }
}

int query(int x)
{
    int temp = 0;
    int val = 0;
    for(int i = 30 ; i >= 0 ; i--)
    {
        int u = (x >> i) & 1;
        if(p[temp][!u])
        {
            val = val * 2 + !u;
            temp = p[temp][!u];
        }
        else
        {
            val = val * 2 + u;
            temp = p[temp][u];
        }
    }
    return val ^ x;
}

int main()
{
    scanf("%d",&n);
    memset(h,-1,sizeof h);
    for(int i = 0 ; i < n ; i++)
    {
        int l,r,w;
        scanf("%d %d %d",&l,&r,&w);
        add(l,r,w);
        add(r,l,w);
    }
    dfs(0,-1,0);
    
    int max1 = 0;
    //共n个节点
    for(int i = 0; i < n ; i++)
    {
        insert(a[i]);
        max1 = max(max1,query(a[i]));
    }
    printf("%d\n",max1);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值