可持久化 01Trie

12 篇文章 0 订阅
3 篇文章 0 订阅

P4551 最长异或路径 (01Trie)

链接:https://www.luogu.com.cn/problem/P4551

题意:给定一棵 n n n 个点的带权树,结点下标从 1 开始到 n 。寻找树中找两个结点,求最长的异或路径。异或路径指的是指两个结点之间唯一路径上的所有边权的异或。

思路: 01trie 模板题

  • 从高位开始贪心,每次枚举一个数字时,需要关心与它异或相反的位置。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;

int n;
int dis[maxn],trie[maxn*30][2],no;
int head[maxn],ecnt=1;
struct Edge
{
    int nxt,to,w;
} edges[maxn<<1];
void add(int u,int v,int w)
{
    edges[++ecnt]= {head[u],v,w};
    head[u]=ecnt;
}
void dfs(int u,int fa)
{
    for(int i=head[u]; i!=-1; i=edges[i].nxt)
    {
        int v=edges[i].to,w=edges[i].w;
        if(v==fa) continue;
        dis[v]=dis[u]^w;
        dfs(v,u);
    }
}
void insert(int rt,int val,int depth)
{
    if(depth<0) return;
    int c=val>>depth&1;
    if(!trie[rt][c]) trie[rt][c]=++no;
    insert(trie[rt][c],val,depth-1);
}
int query(int rt,int val,int depth)
{
    if(depth<0) return 0;
    int c=val>>depth&1;
    if(trie[rt][c^1]) return (1<<depth)+query(trie[rt][c^1],val,depth-1);
    else return query(trie[rt][c],val,depth-1);
}
int main()
{
    memset(head,-1,sizeof(head));
    ecnt=1,no=0;
    scanf("%d",&n);
    for(int i=1; i<=n-1; ++i)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,0);
    for(int i=1; i<=n; ++i) insert(0,dis[i],30);
    int ans=0;
    for(int i=1; i<=n; ++i) ans=max(ans,query(0,dis[i],30));
    printf("%d\n",ans);
    return 0;
}
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;

int n;
int dis[maxn],trie[maxn*30][2],no;
int head[maxn],ecnt=1;
struct Edge
{
    int nxt,to,w;
} edges[maxn<<1];
void add(int u,int v,int w)
{
    edges[++ecnt]= {head[u],v,w};
    head[u]=ecnt;
}
void dfs(int u,int fa)
{
    for(int i=head[u]; i!=-1; i=edges[i].nxt)
    {
        int v=edges[i].to,w=edges[i].w;
        if(v==fa) continue;
        dis[v]=dis[u]^w;
        dfs(v,u);
    }
}
void insert(int x)
{
    int p=0;
    for(int i=(1<<30); i>0; i>>=1)
    {
        bool c=x&i;
        if(!trie[p][c]) trie[p][c]=++no;
        p=trie[p][c];
    }
}
int query(int x)
{
    int p=0,ans=0;
    for(int i=(1<<30); i>0; i>>=1)
    {
        bool c=x&i;
        if(trie[p][c^1]) ans+=i,p=trie[p][c^1];
        else p=trie[p][c];
    }
    return ans;
}
int main()
{
    memset(head,-1,sizeof(head));
    ecnt=1,no=0;
    scanf("%d",&n);
    for(int i=1; i<=n-1; ++i)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,0);
    for(int i=1; i<=n; ++i) insert(dis[i]);
    int ans=0;
    for(int i=1; i<=n; ++i) ans=max(ans,query(dis[i]));
    printf("%d\n",ans);
    return 0;
}

P4735 最大异或和 (可持久化01Trie)

链接:https://www.luogu.com.cn/problem/P4735

题意:给定一个长度为 n 的序列,维护两种操作(共有 m 个操作)

  • A x : 添加操作,表示在序列末尾添加一个数 x x x,序列的长度变为 n+1
  • Q l r x:询问操作,你需要找到一个位置 p ,满足 l ≤ p ≤ r l \le p \le r lpr ,使得 a [ p ] ⊕ a [ p + 1 ] ⊕ . . . ⊕ a [ n ] ⊕ x a[p] \oplus a[p+1] \oplus ... \oplus a[n] \oplus x a[p]a[p+1]...a[n]x 的值最大,输出最大值

1 ≤ n , m ≤ 3 × 1 0 5 , 1 ≤ a i ≤ 1 0 7 1\le n,m\le 3\times 10^5,1\le a_i\le 10^7 1nm3×1051ai107

思路: 用 s [ n ] s[n] s[n] 表示异或前缀和,相当于使 s [ p − 1 ] ⊕ s [ n ] ⊕ x s[p-1]\oplus s[n]\oplus x s[p1]s[n]x 的值最大 , s [ p − 1 ] s[p-1] s[p1] 在变化,而 s [ n ] ⊕ x s[n]\oplus x s[n]x 是固定的,所以可以用 01Trie 维护异或前缀和 s [ n ] s[n] s[n]

  • 对于操作二,相当于查询在 r o o t [ l − 2 ] root[l-2] root[l2] r o o t [ r − 1 ] root[r-1] root[r1] 两棵 01Trie 树之间找一个数,和 s [ n ] ⊕ x s[n]\oplus x s[n]x 异或,使得值最大。同时需要注意 l == 1 的情况,这样可以用到 0 这个节点,这个节点是空的,可以直接得到前缀和
  • 对于操作一,直接添加在末尾即可
  • 复杂度分析:首先初始序列为 3 e 5 3e5 3e5,然后有 m m m 个操作,因此最多有 6 e 5 6e5 6e5次 插入操作。每次操作更新的点数为 l o g 2 ( 1 0 7 ) log_2(10^7) log2(107),因此时空复杂度为: ( n + m ) l o g 2 ( 1 0 7 ) (n + m) log_2(10^7) (n+m)log2(107) 大约是 O ( 24 ( n + m ) ) O(24(n + m)) O(24(n+m))
  • 前缀和这种东西,需要处理 s [ 0 ] s[0] s[0] 出现的情况,这里是对它也建了一棵树
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=6e5+5;
int n,m,a[maxn];
int root[maxn],trie[maxn*28][2],cnt[maxn*28],no,depth=25;
int update(int pre,int depth,int val)
{
    int rt=++no;
    if(depth<0) return rt;//不能返回0,不然cnt[0]会变化
    int c=val>>depth&1;
    trie[rt][c]=update(trie[pre][c],depth-1,val);
    trie[rt][c^1]=trie[pre][c^1];
    cnt[trie[rt][c]]=cnt[trie[pre][c]]+1;
    return rt;
}
int query(int pre,int now,int depth,int val)
{
    if(depth<0) return 0;
    int c=val>>depth&1;
    if(cnt[trie[now][c^1]]-cnt[trie[pre][c^1]]>0)
        return (1<<depth)+query(trie[pre][c^1],trie[now][c^1],depth-1,val);
    else return query(trie[pre][c],trie[now][c],depth-1,val);
}
int main()
{
    int x;
    scanf("%d%d",&n,&m);
    root[0]=update(0,depth,0);
    for(int i=1; i<=n; ++i)
    {
        scanf("%d",&x);
        a[i]=a[i-1]^x;
        root[i]=update(root[i-1],depth,a[i]);
    }
    while(m--)
    {
        char op[4];
        scanf("%s",op);
        if(op[0]=='A')
        {
            scanf("%d",&x);
            n++;
            a[n]=a[n-1]^x;
            root[n]=update(root[n-1],depth,a[n]);
        }
        else
        {
            int l,r,x,ans;
            scanf("%d%d%d",&l,&r,&x);
            if(l==1) ans=query(0,root[r-1],depth,x^a[n]);
            else ans=query(root[l-2],root[r-1],depth,x^a[n]);
            printf("%d\n",ans);
        }
    }
    return 0;
}

P4592 [TJOI2018]异或

链接:https://www.luogu.com.cn/problem/P4592

题意:现在有一颗以 1 为根节点的由 n 个节点组成的树,节点从 1 至 n 编号。树上每个节点上都有一个权值 v i v_i vi 。现在有 q 次操作,操作如下:

  • 1 x z:查询节点 x 的子树中的节点权值与 z 异或结果的最大值。
  • 2 x y z:查询节点 x 到节点 y 的简单路径上的节点的权值与 z 异或结果最大值。

对于 100% 的数据,保证 1 < n , q ≤ 1 0 5 , 1 ≤ u , v , x , y ≤ n , 1 ≤ o p ≤ 2 , 1 ≤ v i , z < 2 30 1< n, q \leq10^5,1 \leq u, v, x, y \leq n,1 \leq op \leq 2,1 \leq v_i, z \lt 2^{30} 1<n,q1051u,v,x,yn1op21vi,z<230

思路

  • 可以树链剖分写吧,时间复杂度是 n ( l o g 2 n ) 2 n(log_2n)^2 n(log2n)2
  • 对于操作一可以用可持久化01Trie 维护 dfs 序实现,对于操作二,可以用另外一颗可持久化01Trie 维护叶节点到根的前缀和,然后用差分实现。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;

int n,m;
int a[maxn],val[maxn];
vector<int> e[maxn];
int dfn[maxn],id[maxn],sz[maxn],times=0;

void dfs1(int u,int fa)
{
    dfn[u]=++times;
    id[times]=u;
    sz[u]=1;
    for(auto v: e[u])
    {
        if(v==fa) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
    }
}
int root1[maxn],trie1[maxn*35][2],cnt1[maxn*35],no1=0;
int update1(int pre,int val,int depth)
{
    int rt=++no1;
    if(depth<0) return rt;
    int c=val>>depth&1;
    trie1[rt][c]=update1(trie1[pre][c],val,depth-1);
    trie1[rt][c^1]=trie1[pre][c^1];
    cnt1[trie1[rt][c]]=cnt1[trie1[pre][c]]+1;
    return rt;
}
ll query1(int pre,int now,int val,int depth)
{
    if(depth<0) return 0;
    int c=val>>depth&1;
    if(cnt1[trie1[now][c^1]]>cnt1[trie1[pre][c^1]])
        return (1ll<<depth)+query1(trie1[pre][c^1],trie1[now][c^1],val,depth-1);
    else return query1(trie1[pre][c],trie1[now][c],val,depth-1);
}

int root2[maxn],trie2[maxn*35][2],cnt2[maxn*35],no2=0;
int depth[maxn],fa[maxn][31];

int update2(int pre,int val,int depth)
{
    int rt=++no2;
    if(depth<0) return rt;
    int c=val>>depth&1;
    trie2[rt][c]=update2(trie2[pre][c],val,depth-1);
    trie2[rt][c^1]=trie2[pre][c^1];
    cnt2[trie2[rt][c]]=cnt2[trie2[pre][c]]+1;
    return rt;
}
ll query2(int x,int y,int u,int v,int val,int depth)
{
    if(depth<0) return 0;
    int c=val>>depth&1;
    int num=cnt2[trie2[x][c^1]]+cnt2[trie2[y][c^1]]-cnt2[trie2[u][c^1]]-cnt2[trie2[v][c^1]];
    if(num>0)
        return (1ll<<depth)+query2(trie2[x][c^1],trie2[y][c^1],trie2[u][c^1],trie2[v][c^1],val,depth-1);
    else return query2(trie2[x][c],trie2[y][c],trie2[u][c],trie2[v][c],val,depth-1);
}

void dfs2(int u,int f)
{
    root2[u]=update2(root2[f],val[u],30);
    depth[u]=depth[f]+1;
    fa[u][0]=f;
    for(int i=1; i<=20; ++i)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for(auto v: e[u])
    {
        if(v==f) continue;
        dfs2(v,u);
    }
}

int lca(int u,int v)
{
    if(depth[u]<depth[v]) swap(u,v);
    int dis=depth[u]-depth[v];
    for(int i=0; (1<<i)<=dis; ++i)
        if(dis>>i&1) 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];
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; ++i) scanf("%d",&val[i]);
    for(int i=1; i<=n-1; ++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1,0);//dfs序
    root1[0]=update1(0,0,30);
    for(int i=1; i<=n; ++i)
        root1[i]=update1(root1[i-1],val[id[i]],30);

    root2[0]=update2(0,0,30);
    dfs2(1,0);

    int op,x,y,z;
    while(m--)
    {
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d",&x,&z);
            int l=dfn[x],r=dfn[x]+sz[x]-1;
            ll ans=query1(root1[l-1],root1[r],z,30);
            printf("%lld\n",ans);
        }
        else
        {
            scanf("%d%d%d",&x,&y,&z);
            int u=lca(x,y);
            int v=fa[u][0];
            ll ans=query2(root2[x],root2[y],root2[u],root2[v],z,30);
            printf("%lld\n",ans);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值