可持久化01Trie学习笔记

前置知识

字典树。

定义

可持久化 Trie 的方式和可持久化线段树的方式是相似的,即每次只修改被添加或值被修改的节点,而保留没有被改动的节点,在上一个版本的基础上连边,使最后每个版本的 Trie 树的根遍历所能分离出的 Trie 树都是完整且包含全部信息的。

大部分的可持久化 Trie 题中,Trie 都是以 01-Trie 的形式出现的。

引入

P4735 最大异或和

\(s_i=a_i\oplus a_{i+1}\oplus\cdots\oplus a_n\)

询问即在 \([l,r]\) 中找到 \(\oplus x\) 最大的树。

考虑 01-Trie 把所有 \(s_i\) 扔上去,把 \(x\) 放到树上跑。每次判断某节点 \(p\) 是否有 \([l,r]\) 的节点经过。

考虑前缀和记录。设 \(val_{i,p}\) 表示 \([1,i]\) 中经过 \(p\) 的数量,即判断 \(val_{r,p}-val_{l-1,p}\) 是否为正。

空间爆炸,可持久化。

考虑加入一个点 \(u\),最多 \(\log V\) 个节点发生改变,新建一下即可。

由于这 \(n\) 个数的加入本身就是一个一个加的,所以操作 1 就简单了。但是加 \(1\) 会导致不好维护后缀异或和,改成前缀,则为 \(s_n\oplus x\) 异或上一个 \([l-1,r-1]\)\(s_i\) 的最大值。

定义

rt[i] cnt val[u] c[u][0/1]
\(i\) 棵树根节点 节点个数 经过 \(u\) 节点的前缀 \(1,i^{*}\) 节点 \(u\) 左/右儿子编号
\(*\):具体要看 \(u\) 节点在哪棵树上。

注意,如果 \(l=1\),则不可能查到 \(-1\) 的树。

考虑整体右移 \(1\),若 \(l=1\) 即代表可以有 \(0\) 这个选项,在一开始插入 \(0\) 即可。

插入

void ins(int x)//将数值x插入b树
{
	rt[++tot]=++cnt;
	int u=rt[tot],u2=rt[tot-1];//上一个版本是b-1
	ff(i,31,0)
	{
		int v=(x>>i)&1;
		c[u][v]=++cnt;
		c[u][!v]=c[u2][!v];
		u=c[u][v],u2=c[u2][v];
		val[u]=val[u2]+1;
	}
}

询问

int ask(int l,int r,int x)
{
	int ans=0;
	int u=rt[r],u2=rt[l-1];
	ff(i,31,0)
	{
		int v=(x>>i)&1;
		if(val[c[u][!v]]-val[c[u2][!v]]>0)
		{
			u=c[u][!v],u2=c[u2][!v];
			ans+=1<<i;
		}
		else u=c[u][v],u2=c[u2][v];
	}
	return ans;
}

完整代码

#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int V=2e7+10,N=3e5+10;
int n,m,cnt,c[V][2],val[V],rt[V],tot;
void ins(int x)//将数值x插入b树
{
	rt[++tot]=++cnt;
	int u=rt[tot],u2=rt[tot-1];//上一个版本是b-1
	ff(i,31,0)
	{
		int v=(x>>i)&1;
		c[u][v]=++cnt;
		c[u][!v]=c[u2][!v];
		u=c[u][v],u2=c[u2][v];
		val[u]=val[u2]+1;
	}
}
int ask(int l,int r,int x)
{
	int ans=0;
	int u=rt[r],u2=rt[l-1];
	ff(i,31,0)
	{
		int v=(x>>i)&1;
		if(val[c[u][!v]]-val[c[u2][!v]]>0)
		{
			u=c[u][!v],u2=c[u2][!v];
			ans+=1<<i;
		}
		else u=c[u][v],u2=c[u2][v];
	}
	return ans;
}
void solve()
{
	ins(0);
	n=read(),m=read();
	int sum=0;
	F(i,1,n) ins(sum^=read());
	while(m--)
	{
		char op[5];
		scanf("%s",op);
		if(op[0]=='A')
		{
			ins(sum^=read());
		}
		else
		{
			int l=read(),r=read();
			put(ask(l,r,sum^read()));
		}
	}
	
}
int main()
{
	int T=1;
//	T=read();
	while(T--) solve();
    return 0;
}

例题

TJOI2018 异或

询问1可以维护dfs序的可持久化01Trie,询问二可以维护每个点到根的可持久化01Trie。

对于询问1,dfs序上找区间即可。对于询问2用树上差分即可。

#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define MIN(x,y) (x<y?x:y)
#define MAX(x,y) (x>y?x:y)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=1e5+10,V=1.5e7+10;
int n,q,rt[N],val[V],c[V][2][2];
int rt1[N],rt2[N];//·?±e′ú±í?ùμ?xoídfsDòμ?
int tot,d[N],dfn[N],num,rev[N];
struct node
{
	int nex;
	int to;
}a[N<<1];
int cnt,head[N];
void add(int u,int v)
{
	a[++cnt].nex=head[u];
	head[u]=cnt;
	a[cnt].to=v;
}
int fa[N],dep[N],f[N][33],siz[N];
void insert(int x,int op)//2?è?d[x]
{
	int u1,u2;
	if(op) rt1[x]=++tot,u1=rt1[x],u2=rt1[fa[x]];//op=1,rt1是处理询问2的 
	else rt2[x]=++tot,u1=rt2[x],u2=rt2[rev[dfn[x]-1]];
	x=d[x];
	ff(i,31,0)
	{
		int v=(x>>i)&1;
		c[u1][v][op]=++tot;
		val[c[u1][v][op]]=val[c[u2][v][op]]+1;
		c[u1][!v][op]=c[u2][!v][op];
		u1=c[u1][v][op],u2=c[u2][v][op];
	}
}
void dfs(int u,int da)
{
	dfn[u]=++num;rev[num]=u;dep[u]=dep[da]+1;
	f[u][0]=fa[u]=da;
	F(i,0,31) f[u][i+1]=f[f[u][i]][i];
	insert(u,1);
	insert(u,0);
	siz[u]=1;
	for(int i=head[u];i;i=a[i].nex)
	{
		int v=a[i].to;
		if(v==da) continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
}
int lca(int x,int y)
{
	if(dep[x]<dep[y]) sd swap(x,y);
	ff(i,31,0)
	{
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
		if(x==y) return x;
	}
	ff(i,31,0)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
int ask1(int b,int x)
{
	int u1=rt2[rev[dfn[b]-1]],u2=rt2[rev[dfn[b]+siz[b]-1]];
	int ans=0;
	ff(i,31,0)
	{
		int v=(x>>i)&1;
		if(val[c[u2][!v][0]]-val[c[u1][!v][0]]>0)
		{
			ans+=1<<i;
			u1=c[u1][!v][0];
			u2=c[u2][!v][0];
		}
		else u1=c[u1][v][0],u2=c[u2][v][0];
	}
	return ans;
}
int ask2(int b,int x,int y)
{
	int u1=rt1[x],u2=rt1[y],u3=rt1[fa[lca(x,y)]],u4=rt1[lca(x,y)];
	int ans=0;
	ff(i,31,0)
	{
		int v=(b>>i)&1;
		if(val[c[u1][!v][1]]+val[c[u2][!v][1]]-val[c[u3][!v][1]]-val[c[u4][!v][1]]>0)
		{
			ans+=1<<i;
			u1=c[u1][!v][1];
			u2=c[u2][!v][1];
			u3=c[u3][!v][1];
			u4=c[u4][!v][1];
		}
		else
		{
			u1=c[u1][v][1];
			u2=c[u2][v][1];
			u3=c[u3][v][1];
			u4=c[u4][v][1];
		}
	}
	return ans;
}
void solve()
{
	n=read(),q=read();
	F(i,1,n) d[i]=read();
	F(i,1,n-1)
	{
		int x=read(),y=read();
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	
	while(q--)
	{
		int op=read(),x=read(),y,z;
		if(op==1)
		{
			z=read();
			put(ask1(x,z));
		}
		else if(op==2)
		{
			y=read(),z=read();
			put(ask2(z,x,y));
		}
	}
}
int main()
{
	int T=1;
//	T=read();
	while(T--) solve();
    return 0;
}

参考资料

博客园-可持久化01Tire

luogu-Trie与可持久化Trie

posted @ 2025-03-07 16:58  _E_M_T  阅读(110)  评论(0)    收藏  举报