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

本文介绍了一种使用树剖和两棵可持久化Trie来解决特定类型的区间异或最大值查询问题的方法。具体包括如何通过树剖进行节点编号,如何构建可持久化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<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long lt;
 
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=500010;
int n,m;
struct node{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int val[maxn],size[maxn],top[maxn],dep[maxn];
int fa[maxn],son[maxn],num[maxn],pos[maxn],cnt;
int rt[2][maxn<<4],sz[2],sum[2][maxn<<4],nxt[2][maxn<<4][2];

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; pos[cnt]=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 cntt,int p)
{
    int rt=++sz[p]; sum[p][rt]=sum[p][pre]+1;
    if(cntt<0) return rt;
    int d=x>>cntt&1;
    nxt[p][rt][d^1]=nxt[p][pre][d^1];
    nxt[p][rt][d]=update(nxt[p][pre][d],x,cntt-1,p);
    return rt;
}

int query1(int u,int v,int x,int cntt)
{
    if(cntt<0) return 0;
    int d=x>>cntt&1;
    int ss=sum[0][nxt[0][v][d^1]]-sum[0][nxt[0][u][d^1]];
    if(ss>0) return (1<<cntt)+query1(nxt[0][u][d^1],nxt[0][v][d^1],x,cntt-1);
    else return query1(nxt[0][u][d],nxt[0][v][d],x,cntt-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]];
    }
    return dep[u]<dep[v]?u:v;
}

void dfs(int u)
{
    rt[1][u]=update(rt[1][fa[u]],val[u],30,1);
    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 cntt)
{
    if(cntt<0) return 0;
    int d=x>>cntt&1;
    int lcu=nxt[1][u][d^1],lcv=nxt[1][v][d^1],lc=nxt[1][lca][d^1],lcg=nxt[1][gra][d^1];
    int ss=sum[1][lcu]+sum[1][lcv]-sum[1][lc]-sum[1][lcg];
    if(ss>0) return (1<<cntt)+query2(lcu,lcv,lc,lcg,x,cntt-1);
    else return query2(nxt[1][u][d],nxt[1][v][d],nxt[1][lca][d],nxt[1][gra][d],x,cntt-1);
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;++i) val[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);//树剖建树
    for(int i=1;i<=n;++i)
    rt[0][i]=update(rt[0][i-1],val[pos[i]],30,0);
    
    dfs(1);//深搜建树
    
    while(m--)
    {
        int opt=read(),x=read(),y=read();
        if(opt==1) printf("%d\n",query1(rt[0][num[x]-1],rt[0][num[x]+size[x]-1],y,30));
        else if(opt==2)
        {
            int z=read(),lca=LCA(x,y);
            printf("%d\n",query2(rt[1][x],rt[1][y],rt[1][lca],rt[1][fa[lca]],z,30));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值