BZOJ5338 || 洛谷P4592 [TJOI2018]xor【可持久化Trie】

Time Limit: 30 Sec
Memory Limit: 256 MB

Description

现在有一颗以1为根节点的由n个节点组成的树,树上每个节点上都有一个权值vi。
现在有Q 次操作,操作如下:
1 x y 查询节点x的子树中与y异或结果的最大值
2 x y z 查询路径x到y上点与z异或结果最大值

Input

第一行是两个数字n, Q;
第二行是n个数字用空格隔开,第i个数字vi表示点i上的权值
接下来n-1行,每行两个数,x,y,表示节点x与y之间有边
接下来Q行,每一行为一个查询,格式如上所述.
1 < n, Q ≤ 100000 ,查询1中的y ≤ 2^30 ,查询2中的z ≤ 2^30

Output

对于每一个查询,输出一行,表示满足条件的最大值。


题目分析

区间异或最大值,马上想到可持久化Trie

但是这两个操作要怎么建到一棵树啊啊啊啊
深思熟虑了好久发现还是得建两棵树

对于1询问
对原树树剖一下
利用树剖编号建立可持久化Trie
即维护按编号顺序的前缀

对于询问2
每个结点建可持久化Trie维护该点到根的路径
u~v的路径利用差分
u的Trie+v的Trie-lca(u,v)的Trie-fa[lca(u,v)]的Trie


由于建了两棵可持久化Trie
代码有些乱=_=

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=1e5+10;
int n,m;
int a[maxn],b[maxn];
struct node{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int fa[maxn],dep[maxn],size[maxn],cnt;
int top[maxn],num[maxn],pos[maxn],son[maxn];
int rt[2][maxn],sz[2],nxt[2][maxn<<5][2],sum[2][maxn<<5];
//这些数组第一维1表示第一个询问的,0表示第二个询问的

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot; 
}

void dfs1(int u,int pa)//树剖
{
    size[u]=1;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==pa) continue;
        dep[v]=dep[u]+1; fa[v]=u;
        dfs1(v,u);
        size[u]+=size[v];
        if(size[v]>size[son[u]]) son[u]=v;
    }
}

void dfs2(int u,int tp)
{
    top[u]=tp; num[u]=++cnt; b[cnt]=a[u];
    if(son[u]) dfs2(son[u],tp);
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v); 
    }
}

int update(int pre,int x,int cnt,int p)//可持久化Trie建树
{
    int rt=++sz[p]; sum[p][rt]=sum[p][pre]+1;
    if(cnt<0) return rt;
    int d=x>>cnt&1;
    nxt[p][rt][d^1]=nxt[p][pre][d^1];
    nxt[p][rt][d]=update(nxt[p][pre][d],x,cnt-1,p);
    return rt;
}

void dfs(int u)//深搜建树
{
    rt[0][u]=update(rt[0][fa[u]],a[u],30,0);
    for(int i=head[u];i;i=E[i].nxt)
    if(E[i].v!=fa[u]) dfs(E[i].v);
}

int query2(int u,int v,int lca,int gra,int x,int cnt)
{
    if(cnt<0) return 0;
    int d=x>>cnt&1;
    int ss=sum[0][nxt[0][u][d^1]]+sum[0][nxt[0][v][d^1]]-sum[0][nxt[0][lca][d^1]]-sum[0][nxt[0][gra][d^1]];
    if(ss>0) return (1<<cnt)+query2(nxt[0][u][d^1],nxt[0][v][d^1],nxt[0][lca][d^1],nxt[0][gra][d^1],x,cnt-1);
    else return query2(nxt[0][u][d],nxt[0][v][d],nxt[0][lca][d],nxt[0][gra][d],x,cnt-1);
}

int query1(int u,int v,int x,int cnt)
{
    if(cnt<0) return 0;
    int d=x>>cnt&1;
    int ss=sum[1][nxt[1][v][d^1]]-sum[1][nxt[1][u][d^1]];
    if(ss>0) return (1<<cnt)+query1(nxt[1][u][d^1],nxt[1][v][d^1],x,cnt-1);
    else return query1(nxt[1][u][d],nxt[1][v][d],x,cnt-1);
}

int LCA(int u,int v)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]>dep[top[v]]) u=fa[top[u]];
        else v=fa[top[v]];
    }
    if(dep[u]<dep[v]) return u;
    else return v;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i) a[i]=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read();
        add(u,v);add(v,u);
    }
    dfs1(1,0); dfs2(1,1);//树剖

    dfs(1);//深搜建树,对于第二个询问
    for(int i=1;i<=n;++i)//按树剖编号建树,第一个询问
    rt[1][i]=update(rt[1][i-1],b[i],30,1);

    while(m--)
    {
        int k=read();
        if(k==1)
        {
            int u=read(),x=read();
            printf("%d\n",query1(rt[1][num[u]-1],rt[1][num[u]+size[u]-1],x,30));
            //第num[u]+size[u]-1与第num[u]-1棵树相减
        }
        else if(k==2)
        {
            int u=read(),v=read(),x=read();
            int lca=LCA(u,v);
            printf("%d\n",query2(rt[0][u],rt[0][v],rt[0][lca],rt[0][fa[lca]],x,30));
            //差分路径
        }
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/niiick/article/details/80440896
今日推荐