HDU

来源:互联网 发布:紫胤真人知乎 编辑:程序博客网 时间:2024/06/09 00:23

点我看题

题意:给出一颗树,每棵树上有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=1600000int 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;}


原创粉丝点击