HDU - 4575 Tree(可持久化字典树)

点我看题

题意:给出一颗树,每棵树上有n个结点,每个结点对应一个值,有m组查询操作,查询在从x到y的这条路径上与z进行异或的最大值。

分析:可持久化字典树的模板题

这个题以01字典树为基础,如果不是很了解01字典树的话,可以看看ACdream1063,这题题解

话说回来,如果我们要查询数x与某个数异或的最大值,我们应该尽量选择高位与x的高位相反的一些数。

举个栗子,我们要求5与某个数异或的最大值,假设可选的数不超过15,5的二进制为0101,因为可选的数不超过15,所以我们只要考虑后四位,

首先看最高位(第四位)存不存在等于1的数,存在的话,一直往低位走,最终可以得到结果。

这个题就要对每个结点都建一棵Trie树(感觉根主席树有点像噢,主席树是对每个结点建线段树

建Trie树的时候,我们用一个数组sz[]记录字典树对应结点前缀的数量,如果v是u的子节点,且v的权值是010,u的权值为011,假设u已经加到树中,那么在加v的时候,发现前缀01的数量已经是1了,那我们只要在这个基础上加1即可,也就是继承了父节点对它的影响,但是对于010来说是一个新的数,我们要新建一个结点然后更新他的sz,其余和父节点一致就好。

当我们去查询每一位都取反的x时,只关心每一位是否对应存在。也就是想知道u到v这条路上有没有满足条件的存在。

设pre=lca(u,v),我们就只要判断f(u)+f(v)-2×f(pre)是否大于0就好,大于0的话,就加上1>>i。

参考代码:

/*可持久化字典树*/
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 1e5+10;//原树结点
const int maxv = 16;//每颗字典树的深度
const int maxnode = 2e6;//字典树的结点个数,16*1e5=1600000
int n,q;
int a[maxn];
vector<int> g[maxn];
//字典树
int tot;//记录字典树的总结点数
int root[maxn];//记录字典树的根节点
int f[maxv+2][maxn];//嘻嘻嘻嘻f[i][v]指的是结点v向上跳2^i次后得到的结点值
int sz[maxnode];//记录前缀和
int dep[maxn];//记录结点深度
int ch[maxnode][2];//字典树儿子结点

void Init()
{
    tot = 0;
    mem(root,0);
    mem(f,0);
    mem(sz,0);//前缀和初始化为0
    dep[0] = 0;
}

int NewNode()
{
    mem(ch[++tot],0);
    return tot;
}

void Insert( int u, int fa, int val)
{
    int rtu = root[u];//root[u]是字典树中真正的位置,而u是在原树中的编号
    int rtf = root[fa];//同理
    for( int i = 15; i >= 0; i--)
    {
        int id = (val>>i)&1;//计算val的第i位(有第0位)是1还是0
        if( !ch[rtu][id])//如果第i位不存在的话,那就要新建一个结点
        {
            ch[rtu][id] = NewNode();
            ch[rtu][!id] = ch[rtf][!id];//!id就继承父节点的
            sz[ch[rtu][id]] = sz[ch[rtf][id]];//前缀也继承父节点的
        }
        rtu = ch[rtu][id];//往下走
        rtf = ch[rtf][id];
        sz[rtu]++;
    }
}

//遍历原树中的n个结点,对每个结点建立01字典树
void dfs( int u, int fa)
{
    f[0][u] = fa;//记录下u的父节点
    dep[u] = dep[fa]+1;//当前结点的深度为其父亲的深度+1
    root[u] = NewNode();//给字典树新建一个结点
    Insert(u,fa,a[u]);//根据结点u对应的值建01字典树
    for( int i = 0; i < g[u].size(); i++)//遍历当前结点u的叶子结点
    {
        int v = g[u][i];
        if( v != fa)//如果不是其父节点,继续深搜
            dfs(v,u);
    }
}

//倍增lca
//求u和v的最近公共祖先
int lca( int u, int v)
{
    if( dep[u] > dep[v])//保证u的深度小于v,也就是u和v的祖先结点在同一深度
        swap(u,v);
    for( int i = 0; i < maxv; i++)
        if( ((dep[v]-dep[u])>>i) & 1)//如果条件为真,就往上跳,最终结果会与u在同一层
            v = f[i][v];
    if( u == v)//显然
        return u;
    for( int i = maxv-1; i >= 0; i--)
        if( f[i][u] != f[i][v])//还不相等的话,就一起往上走
            u = f[i][u], v = f[i][v];
    return f[0][u];//最后u==v,返回自己
}

int Query( int u, int v, int val)
{
    int f = lca(u,v);//求u,v的最近公共祖先
    int res = a[f]^val;
    int rtu = root[u], rtv = root[v], rtf = root[f];//转化到字典树中去
    int ret = 0;
    for( int i = maxv-1; i >= 0; i--)
    {
        int id = (val>>i)&1;
        if( sz[ch[rtu][!id]]+sz[ch[rtv][!id]]-2*sz[ch[rtf][!id]] > 0)//满足这个条件
        {
            ret += 1<<i;
            id = !id;
        }
        rtu = ch[rtu][id];
        rtv = ch[rtv][id];
        rtf = ch[rtf][id];
    }
    return max(ret,res);
}

int main()
{
    while( ~scanf("%d%d",&n,&q))
    {
        Init();
        for( int i = 1; i <= n; i++)
        {
            scanf("%d",&a[i]);//输入每个结点的值
            g[i].clear();//清空动态数组
        }
        int u,v;
        for( int i = 1; i < n; i++)
        {
            scanf("%d%d",&u,&v);//输入一条边
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs(1,0);

        for( int i = 0; i < maxv-1; i++)
            for( int j = 1; j <= n; j++)
                if( f[i][j] != 0)
                    f[i+1][j] = f[i][f[i][j]];//j向上跳2^(i+1)次得到的结点相当于j先向上跳2^i再向上跳2^i
        int x,y,z;
        while( q--)
        {
            scanf("%d%d%d",&x,&y,&z);
            printf("%d\n",Query(x,y,z));
        }
    }

    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值