GDCPC2018广东省赛C题

题目大概意思

给出一棵树,树上的每个结点的值。
询问从结点a到b的路径上的值,对于值val来说,最大的异或和是多少

这是一道经典可持久化字典树的题目

#include<bits/stdc++.h>
using namespace std;

const int N=2e5+5,M=30;

int ch[N*35][2],tot,num[N*32],a[N],T[N];
int fa[N][21],dep[N],n,m;
vector<int>g[N];

void add(int now,int last,int val)//在结点now继承父亲的属性构造可持久化字典树
{
    for(int i=M;i>=0;i--)//分解结点val的值
    {
        int id=(val>>i)&1;
        ch[now][id]=++tot;
        now=ch[now][id],last=ch[last][id];//父亲结点和该结点的字典树结点同时向下,用于构造该结点的字典树的时候
        memcpy(ch[now],ch[last],sizeof(ch[last]));//继承父亲的值
        num[now]=num[last]+1;//基础上加一,因为多了该节点
    }
}

void dfs(int u,int father,int d)
{
    fa[u][0]=father,dep[u]=d;//lca的预处理
    for(int i=1;i<=20;i++)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    T[u]=++tot;//tot指的是字典树的结点,T[u]表示,u结点字典树的根,即入口
    memset(ch[tot],0,sizeof(ch[tot]));//初始化字典树
    num[tot]=0;
    add(T[u],T[father],a[u]);//构造持久化字典树
    for(int v:g[u])
    {
        if(v==father) continue;
        dfs(v,u,d+1);
    }
}

int work(int u,int v,int lca,int lcaf,int x)
{
    int res=0;
    for(int i=M;i>=0;i--)//关键,对于询问值来说,要想得到最大值,对于每一位来说,位操作都是独立的,所以可以自高向低位贪心每一位最大
    {
        int id=!((x>>i)&1);//期望贪心字典树的位
        if(num[ch[u][id]]+num[ch[v][id]]-num[ch[lca][id]]-num[ch[lcaf][id]])//关键一步,lcaf指的是lca的父亲,至于为什么这样操作,画图便知
            res|=(1<<i);//如果有那一位可以进行异或,便下去
        else id^=1;//没有就取不到了
        u=ch[u][id],v=ch[v][id],lca=ch[lca][id],lcaf=ch[lcaf][id];//四个结点的字典树结点,同时下去。
    }
    return res;
}

int lca(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=20;i>=0;i--)
        if(dep[fa[u][i]]>=dep[v])
            u=fa[u][i];
    if(u==v) return u;
    for(int i=20;i>=0;i--)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}

void solve()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),g[i].clear();
    for(int i=2;i<=n;i++)
    {
        int u=i,v;
        scanf("%d",&v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    memset(ch[0],0,sizeof(ch[0]));
    tot=num[0]=0;
    dfs(1,0,1);
    for(int i=1;i<=m;i++)
    {
        int u,v,x;
        scanf("%d %d %d",&x,&u,&v);
        int LCA=lca(u,v);
        int ans=work(T[u],T[v],T[LCA],T[fa[LCA][0]],x);
        printf("%d\n",ans);
    }
}

int main()
{
    //freopen("C.in","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--)
        solve();
    return 0;
}

对于每个结点来说,信息都是继承自父亲结点的信息,那么从根结点下去,就相当于是一个前缀和的形式,所以,可以通过减法,来获取树上某个区间的信息。可持久化字典树需要的空间,相当于每个结点都建立了一颗01字典树。可以开辟。然后,通过求取lca的方式,将两端链拼接在一起,就可以拿到我们想要的信息了,这就是可持久化的精髓所在

猜你喜欢

转载自blog.csdn.net/qq965194745/article/details/80445977