可持久化trie 学习总结

QAQ 以前一直觉得可持久化trie很难,今天强行写了一发觉得还是蛮简单的嘛

自己的模板是自己手写的,写了几道题目并没有出过错误

THUSC的第二题的解法五貌似就是可持久化trie,时间复杂度O(60*n*logn)

不过并没有正解优,听说考场上有人写可持久化树链剖分,也是6得不行QAQ

可持久化trie就是你每次插入一个单词的时候将原来trie的代码每次向下走的时候新建节点

把当前节点信息拷贝给新建节点,通常情况下还要额外对于trie的每个节点维护子树的信息

BZOJ 3261

也算是经典题目了吧,转化成后缀异或和之后变成了选出一个数使得其异或x最大

然后直接在trie上贪心就可以了

至于[L,R]的限制,用可持久化trie维护sz域,然后每次判断的时候减一减看看是不是为0就可以了

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
 
int n,m,tot,now,blo;
int L,R,x;
int a[300010];
int rt[600010];
int nxt[16000010][2];
int sz[16000010];
 
void read(int &num){
    num=0;char ch=getchar();
    while(ch<'!')ch=getchar();
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void insert(int &o,int v,int dep){
    ++tot;
    nxt[tot][0]=nxt[o][0];
    nxt[tot][1]=nxt[o][1];
    sz[tot]=sz[o]+1;o=tot;
    if(dep==-1)return;
    if(v>>dep&1)insert(nxt[o][1],v,dep-1);
    else insert(nxt[o][0],v,dep-1);
}
int Get_ans(int a,int b,int v){
    int ans=0;
    for(int i=24;i>=0;--i){
        if(v>>i&1){
            int A=nxt[a][0],B=nxt[b][0];
            if(sz[B]-sz[A]>0)ans|=(1<<i),a=A,b=B;
            else a=nxt[a][1],b=nxt[b][1];
        }else{
            int A=nxt[a][1],B=nxt[b][1];
            if(sz[B]-sz[A]>0)ans|=(1<<i),a=A,b=B;
            else a=nxt[a][0],b=nxt[b][0];
        }
    }
    return ans;
}
int main(){
    read(n);read(m);
    for(int i=1;i<=n;++i)read(a[i]);
    for(int i=n-1;i>=1;--i)a[i]^=a[i+1];
    rt[0]=1;tot=1;
    for(int i=1;i<=n;++i){
        rt[i]=rt[i-1];
        insert(rt[i],a[i],24);
    }blo=n;
    while(m--){
        char ch=getchar();
        while(ch<'!')ch=getchar();
        if(ch=='A'){
            read(x);rt[blo+1]=rt[blo];blo++;
            now^=x;x^=now;
            insert(rt[blo],x,24);
        }else{
            read(L);read(R);
            read(x);x^=now;
            printf("%d
",Get_ans(rt[L-1],rt[R],x));
        }
    }return 0;
}

BZOJ 3166

看完题之后发现只要知道每个点作为次小值的可能区间就转化成了上面的问题

至于求可行区间就是51Nod的 移数博弈 的原题了QAQ

直接用链表搞一搞就搞出来了,查询用可持久化trie就可以了

其实我的写法略微有些蠢

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;
 
const int maxn=50010;
int n,tot=1;
int sub[maxn],pre[maxn];
struct Num{
    int v,id;
}t[maxn];
int rt[maxn];
int nxt[2000010][2];
int sz[2000010];
int ans=0;
void read(int &num){
    num=0;char ch=getchar();
    while(ch<'!')ch=getchar();
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
bool cmp(const Num &A,const Num &B){
    return A.v<B.v;
}
void insert(int &o,int v,int dep){
    ++tot;
    nxt[tot][0]=nxt[o][0];
    nxt[tot][1]=nxt[o][1];
    sz[tot]=sz[o]+1;o=tot;
    if(dep==-1)return;
    if(v>>dep&1)insert(nxt[o][1],v,dep-1);
    else insert(nxt[o][0],v,dep-1);
}
int ask(int a,int b,int v){
    int ans=0;
    for(int i=31;i>=0;--i){
        int x=(v>>i&1);
        int A=nxt[a][x^1],B=nxt[b][x^1];
        if(sz[B]-sz[A]>0)ans|=(1<<i),a=A,b=B;
        else a=nxt[a][x],b=nxt[b][x];
    }return ans;
}
void del(int now){
    pre[sub[now]]=pre[now];
    sub[pre[now]]=sub[now];
}
 
int main(){
    read(n);
    for(int i=1;i<=n;++i)read(t[i].v),t[i].id=i;
    rt[0]=1;
    for(int i=1;i<=n;++i){
        rt[i]=rt[i-1];
        insert(rt[i],t[i].v,31);
    }
    sort(t+1,t+n+1,cmp);
    for(int i=1;i<=n;++i)sub[i]=i+1,pre[i]=i-1;
    pre[0]=0;sub[n+1]=n+1;sub[0]=1;pre[n+1]=n;
    for(int i=1;i<=n;++i){
        int now=t[i].id;
        int L=pre[now],R=sub[now];
        int l=pre[L],r=sub[R];
        if(l+1<=R-1)ans=max(ans,ask(rt[l],rt[R-1],t[i].v));
        if(L+1<=r-1)ans=max(ans,ask(rt[L],rt[r-1],t[i].v));
        del(now);
    }printf("%d
",ans);
    return 0;
}

BZOJ 2741

一开始看到题目一脸懵逼,之后看到数据范围就秒懂了做法

显然数据范围这么小,O(30*n*sqrt(n))也不会挂

转化成前缀和之后是很经典的问题

我们直接分块,预处理f[i][j]表示i->j这些块内的答案

每次询问只需要考虑散点就可以了,直接暴力然后在可持久化trie上跑一跑就可以了

我的预处理写的略微麻烦了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
using namespace std;

const int maxn=20010;
typedef long long LL;
int n,m,tot,blo,k;
int nxt[1000010][2];
int sz[1000010];
int pos[maxn];
int rt[maxn];
int Ans[210][210];
LL a[maxn];
LL x,y;
LL la;

int Get_Node(){
	tot++;
	nxt[tot][0]=nxt[tot][1]=0;
	return tot;
}
int Get_ask(int v){
	int ans=0,now=1;
	for(int i=31;i>=0;--i){
		int x=(v>>i&1);
		if(nxt[now][x^1])ans|=(1<<i),now=nxt[now][x^1];
		else now=nxt[now][x];
	}return ans;
}
void Get_insert(int v){
	int now=1;
	for(int i=31;i>=0;--i){
		int id=(v>>i&1);
		if(!nxt[now][id])nxt[now][id]=Get_Node();
		now=nxt[now][id];
	}return;
}
void insert(int &o,int v,int dep){
	++tot;
	nxt[tot][0]=nxt[o][0];
	nxt[tot][1]=nxt[o][1];
	sz[tot]=sz[o]+1;o=tot;
	if(dep==-1)return;
	if(v>>dep&1)insert(nxt[o][1],v,dep-1);
	else insert(nxt[o][0],v,dep-1);
}
int ask(int a,int b,int v){
	int ans=0;
	for(int i=31;i>=0;--i){
		int x=(v>>i&1);
		int A=nxt[a][x^1],B=nxt[b][x^1];
		if(sz[B]-sz[A]>0)ans|=(1<<i),a=A,b=B;
		else a=nxt[a][x],b=nxt[b][x];
	}return ans;
}
void Get_ans(int u,int v){
	int A=pos[u],B=pos[v];
	la=0;
	if(A==B){
		for(int i=u;i<=v;++i){
			int tmp=ask(rt[u-1],rt[i],a[i]);
			if(tmp>la)la=tmp;	
		}return;
	}
	la=Ans[A+1][B-1];
	int lim=(B-1)*blo+1;
	for(int i=lim;i<=v;++i){
		int tmp=ask(rt[u-1],rt[i],a[i]);
		if(tmp>la)la=tmp;
	}
	lim=A*blo;
	for(int i=u;i<=lim;++i){
		int tmp=ask(rt[i],rt[v],a[i]);
		if(tmp>la)la=tmp;
	}return;
}
int main(){
	scanf("%d%d",&n,&m);
	blo=(int)(sqrt(n));
	k=(n%blo==0?n/blo:n/blo+1);
	for(int i=1;i<=n;++i)scanf("%lld",&a[i]),a[i]^=a[i-1],pos[i]=(i-1)/blo+1;
	for(int i=1;i<=k;++i){
		int tmp=0;tot=1;nxt[tot][0]=nxt[tot][1]=0;
		for(int j=i;j<=k;++j){
			int L=(j-1)*blo+1,R=min(n,j*blo);
			for(int k=L;k<=R;++k){
				Get_insert(a[k]);
				tmp=max(tmp,Get_ask(a[k]));
			}Ans[i][j]=tmp;
		}
	}
	rt[0]=1;tot=1;nxt[tot][0]=nxt[tot][1]=0;
	for(int i=1;i<=n;++i){
		rt[i]=rt[i-1];
		insert(rt[i],a[i],31);
	}
	while(m--){
		scanf("%lld%lld",&x,&y);
		x=(x+la)%n+1;y=(y+la)%n+1;
		if(x>y)swap(x,y);
		x--;Get_ans(x,y);
		printf("%lld
",la);
	}return 0;
}

BZOJ 4260

这么丝薄的题目。。QAQ

想起一道POJ的题目,貌似是求两段不相交的子序列使得总和最大

做法是正着DP一遍,倒着DP一遍,然后枚举分界点

这道题同理,我们正着扫一遍,一边扫一边建trie并维护答案

之后再倒着扫一遍,然后统计答案即可

不过这道题目貌似跟可持久化trie没什么关系..

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

typedef long long LL;
const int maxn=400010;
int n,tot;
int a[maxn],sum[maxn];
int pre[maxn],sub[maxn];
int nxt[13000010][2];
LL ans;

void read(int &num){
	num=0;char ch=getchar();
	while(ch<'!')ch=getchar();
	while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
int Get_Node(){
	tot++;
	nxt[tot][0]=nxt[tot][1]=0;
	return tot;
}
int ask(int v){
	int ans=0,now=1;
	for(int i=31;i>=0;--i){
		int x=(v>>i&1);
		if(!nxt[now][x^1])now=nxt[now][x];
		else now=nxt[now][x^1],ans|=(1<<i);
	}return ans;
}
void insert(int v){
	int now=1;
	for(int i=31;i>=0;--i){
		int id=(v>>i&1);
		if(!nxt[now][id])nxt[now][id]=Get_Node();
		now=nxt[now][id];
	}return;
}

int main(){
	read(n);
	for(int i=1;i<=n;++i)read(a[i]);
	for(int i=1;i<=n;++i)sum[i]=(a[i]^sum[i-1]);
	tot=1;nxt[tot][0]=nxt[tot][1]=0;
	insert(sum[0]);
	for(int i=1;i<=n;++i){
		pre[i]=max(ask(sum[i]),pre[i-1]);
		insert(sum[i]);
	}
	tot=1;nxt[tot][0]=nxt[tot][1]=0;
	insert(sum[n]);
	for(int i=n-1;i>=0;--i){
		sub[i]=max(ask(sum[i]),sub[i-1]);
		insert(sum[i]);
	}
	for(int i=1;i<=n-1;++i){
		LL tmp=sub[i]+pre[i];
		if(tmp>ans)ans=tmp;
	}printf("%lld
",ans);
	return 0;
}

BZOJ 4546

赤裸裸的可持久化trie的模板题啊

由于可持久化所以1,3操作随便写

最大异或和在trie上跑一跑就可以了

至于查询第k大和查询某个数的排名的话,直接在trie上跑,模拟一下就可以了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
 
const int maxn=500010;
int n,type;
int L,R,x,cnt,tot=1;
int rt[maxn];
int nxt[10500010][2];
int sz[10500010];
 
void read(int &num){
    num=0;char ch=getchar();
    while(ch<'!')ch=getchar();
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void insert(int &o,int v,int dep){
    ++tot;
    nxt[tot][0]=nxt[o][0];
    nxt[tot][1]=nxt[o][1];
    sz[tot]=sz[o]+1;o=tot;
    if(dep==-1)return;
    if(v>>dep&1)insert(nxt[o][1],v,dep-1);
    else insert(nxt[o][0],v,dep-1);
}
int ask(int a,int b,int v){
    int ans=0,now=1;
    for(int i=19;i>=0;--i){
        int x=(v>>i&1);
        int A=nxt[a][x^1],B=nxt[b][x^1];
        if(sz[B]-sz[A]>0)ans|=(1<<i),a=A,b=B;
        else a=nxt[a][x],b=nxt[b][x];
    }return ans;
}
int Get_ans(int a,int b,int v){
    int ans=0;
    for(int i=19;i>=0;--i){
        int x=(v>>i&1);
        if(x)ans+=sz[nxt[b][0]]-sz[nxt[a][0]];
        a=nxt[a][x];b=nxt[b][x];
    }ans+=sz[b]-sz[a];
    return ans;
}
int Get_Num(int a,int b,int k){
    int ans=0;
    for(int i=19;i>=0;--i){
        int tmp=sz[nxt[b][0]]-sz[nxt[a][0]];
        if(tmp>=k)a=nxt[a][0],b=nxt[b][0];
        else ans|=(1<<i),a=nxt[a][1],b=nxt[b][1],k-=tmp;
    }return ans;
}
int main(){
    read(n);rt[0]=1;
    while(n--){
        read(type);
        if(type==1){
            read(x);rt[cnt+1]=rt[cnt];cnt++;
            insert(rt[cnt],x,19);
        }else if(type==2){
            read(L);read(R);read(x);
            printf("%d
",ask(rt[L-1],rt[R],x)^x);
        }else if(type==3){
            read(x);cnt=cnt-x;
        }else if(type==4){
            read(L);read(R);read(x);
            printf("%d
",Get_ans(rt[L-1],rt[R],x));
        }else{
            read(L);read(R);read(x);
            printf("%d
",Get_Num(rt[L-1],rt[R],x));
        }
    }return 0;
}

hdu 4757

查询树上u-v的链上的数和w的最大异或和

经典的可持久化数据结构上树

一样的写法,在树上DFS一下就可以了

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

const int maxn=100010;
int n,m,u,v,w,tot,cnt;
int a[maxn],rt[maxn];
int h[maxn],dep[maxn];
int anc[maxn][20];
int nxt[2000010][2];
int sz[2000010];
struct edge{
	int to,next;
}G[maxn<<1];

void add(int x,int y){
	++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
void read(int &num){
	num=0;char ch=getchar();
	while(ch<'!')ch=getchar();
	while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void insert(int &o,int v,int dep){
	++tot;
	nxt[tot][1]=nxt[o][1];
	nxt[tot][0]=nxt[o][0];
	sz[tot]=sz[o]+1;o=tot;
	if(dep==-1)return;
	if(v>>dep&1)insert(nxt[o][1],v,dep-1);
	else insert(nxt[o][0],v,dep-1);
}
int ask(int a,int b,int c,int v,int tmp){
	int ans=0;
	for(int i=15;i>=0;--i){
		int x=(v>>i&1);
		int now=sz[nxt[a][x^1]]+sz[nxt[b][x^1]]-2*sz[nxt[c][x^1]];
		if(now>0)ans|=(1<<i),a=nxt[a][x^1],b=nxt[b][x^1],c=nxt[c][x^1];
		else a=nxt[a][x],b=nxt[b][x],c=nxt[c][x];
	}return max(ans,v^tmp);
}
void DFS(int u,int f){
	anc[u][0]=f;
	rt[u]=rt[f];insert(rt[u],a[u],15);
	for(int i=h[u];i;i=G[i].next){
		int v=G[i].to;
		if(v==f)continue;
		dep[v]=dep[u]+1;
		DFS(v,u);
	}return;
}
void pre_LCA(){
	anc[1][0]=-1;
	for(int i=1;i<=n;++i){
		for(int j=1;(1<<j)<=n;++j)anc[i][j]=-1;
	}
	for(int j=1;(1<<j)<=n;++j){
		for(int i=1;i<=n;++i){
			if(anc[i][j-1]!=-1){
				int a=anc[i][j-1];
				anc[i][j]=anc[a][j-1];
			}
		}
	}return;
}
int LCA(int p,int q){
	if(dep[p]<dep[q])swap(p,q);
	int log;
	for(log=0;(1<<log)<=dep[p];++log);--log;
	for(int i=log;i>=0;--i){
		if(dep[p]-(1<<i)>=dep[q])p=anc[p][i];
	}
	if(p==q)return p;
	for(int i=log;i>=0;--i){
		if(anc[p][i]!=-1&&anc[p][i]!=anc[q][i]){
			p=anc[p][i];q=anc[q][i];
		}
	}return anc[p][0];
}

int main(){
	while(scanf("%d%d",&n,&m)==2){
		for(int i=1;i<=n;++i)read(a[i]);
		memset(h,0,sizeof(h));cnt=0;
		for(int i=1;i<n;++i){
			read(u);read(v);
			add(u,v);add(v,u);
		}
		tot=1;nxt[tot][0]=nxt[tot][1]=0;
		rt[0]=1;DFS(1,0);pre_LCA();
		while(m--){
			read(u);read(v);read(w);
			int lca=LCA(u,v);
			printf("%d
",ask(rt[u],rt[v],rt[lca],w,a[lca]));
		}
	}return 0;
}

可持久化trie总结:

1、多半情况下是二进制trie,用来解决异或和问题

一般用来解决区间异或和,因为子集异或和我们有萌萌哒线性基QAQ

2、内存很容易爆炸,所以在写之前要算内存并且看内存限制

其实可持久化trie很无脑QAQ看出来麻麻麻就可以了

原文地址:https://www.cnblogs.com/joyouth/p/5572495.html