[学习笔记]Trie与可持久化Trie

额,来刚字符串了

一. 最平凡的 \(\operatorname{Trie}\)

长这样

可以发现,这棵字典树用边来代表字母,而从根结点到树上某一结点的路径就代表了一个字符串。
举个例子, \(1\to 4\to 8\to 13\) 表示的就是字符串 \(\mathrm{cab}\)
当然我们也可以看做每个点(除根节点外)有一个点权,这样处理起来更方便
我不会告诉你是我懒得画图了(
问题来了:该如何建树?
显然地,用一个结构体存所有的内容

struct Trie{
    int son[26];
    bool tag;
}trie[WR];
int root,tot;

然后对于插入字符串,有如下代码:(默认字符串全都是小写字母)

bool insrt(char *str){
    int pos=root;//先从根节点开始扫
    for(int i=0;i<strlen(str);i++){//扫每一位
        int val=str[i]-'a';//方便讨论,这样一个点最多有26个孩子
        if(!trie[pos].son[val]) trie[pos].son[val]=++tot;//还没编号就编一个号
        pos=trie[pos].son[val];//更新目前位置,向下走一层
    }
    int mark=trie[pos].tag;
    trie[pos].tag=true;//打一个结尾标记方便判断
    return mark;//考虑是否有重复的字符串
}

如果我们想知道一个字符串是否在 \(\operatorname{Trie}\) 里面出现过也是非常容易的:

bool query(char *str){
    int pos=root;
    for(int i=0;i<strlen(str);i++){
        int val=str[i]-'a';
        if(!trie[pos].son[val]) return false;//如果不存在这一位就返回false
        pos=trie[pos].son[val];//否则继续向下递归
    }
    return trie[pos].tag;//看看这里是否有标记
}

考虑这道模板题

P8306 【模板】字典树

发现没有区别 \(\cdots\cdots\)
就直接一波硬莽完事

点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain
using namespace std;
const int WR=3000010,INF=1099588621776,mod=1e9+7;
struct Trie{
    int son[63];
    bool tag;
}trie[WR];
char ch[WR];
int n,q;
int cnt[WR];
int root,tot;
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+ch-'0';
        ch=getchar();
    }
    return s*w;
}
int getval(char c){
    if(c>='A'&&c<='Z') return c-'A';
    else if(c>='a'&&c<='z') return c-'a'+26;
    else return c-'0'+52;
}
bool insrt(char *str){
    int pos=root;
    for(int i=0;i<strlen(str);i++){
        int val=getval(str[i]);
        if(!trie[pos].son[val]) trie[pos].son[val]=++tot;
        pos=trie[pos].son[val];
        cnt[pos]++;
    }
    int mark=trie[pos].tag;
    trie[pos].tag=true;
    return mark;
}
int query(char *str){
    int pos=root;
    for(int i=0;i<strlen(str);i++){
        int val=getval(str[i]);
        if(!trie[pos].son[val]) return 0;
        pos=trie[pos].son[val];
    }
    return cnt[pos];
}
signed main(){
    int T=read();
    while(T--){
        for(int i=0;i<=tot;i++) cnt[i]=0,trie[i].tag=false;
        for(int i=0;i<=tot;i++){
            for(int j=0;j<=62;j++){
                trie[i].son[j]=0;
            }
        }
        root=tot=0;
        n=read(),q=read();
        for(int i=1;i<=n;i++){
            scanf("%s",ch);
            insrt(ch);
        }
        for(int i=1;i<=q;i++){
            scanf("%s",ch);
            printf("%lld\n",query(ch));
        }
    }
    return 0;
}

二. \(0/1\;\operatorname{Trie}\)

顾名思义,就是只有 \(0\)\(1\)\(\operatorname{Trie}\)
可以有效地解决异或值的一类问题
比如:

\[\large\textbf{在给定的 n 个整数中选出两个进行异或运算,得到的结果最大是多少?} \]

这时我们考虑用字典树解决问题:
把这些数字转化成二进制扔进字典树里
查询的时候看看能不能走 \(1\) (这样肯定比走 \(0\) 大)就完事了

点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define WR WinterRain
using namespace std;
const int WR=5001000;
int n,m,tot,ans;
int trie[WR][2],cnt[WR];
int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<1)+(s<<3)+ch-'0';
		ch=getchar();
	}
	return s*w;
}
void insert(int x) {
    int p=0;
    for(int i=31;i>=0;i--) {
        int c=(x>>i)&1;
        if(!trie[p][c]) trie[p][c]=++tot;
        p=trie[p][c];
    }
}
int search(int x) {
    int p=0,ans=0;
    for(int i=31;i>=0;i--) {
        int c=(x>>i)&1,o=c^1;
        if(trie[p][o]) p=trie[p][o],ans=ans<<1|1;
        else p=trie[p][c],ans<<=1;
    }
    return ans;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++) {
        int x=read();
        ans=max(ans,search(x));
        insert(x);
    }
    printf("%d",ans);
    return 0;
}

然后考虑这道题目

P4551 最长异或路径

又因为异或满足分配律,因此 \(dis_{u,1}\oplus dis_{v,1}=dis_{u,\operatorname{LCA}}\oplus dis_{v,LCA}=dis_{u,v}\)
没了

点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain 
using namespace std;
const int WR=5001000;
struct Edge{
	int pre,to,val;
}edge[WR>>2];
struct Trie{
    int son[2];
}trie[WR];
int n,ans;
int head[WR>>2],cnt=0;
int ipt[WR>>2],dis[WR>>2];
int tot,root;
int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<3)+(s<<1)+ch-48;
		ch=getchar();
	}
	return s*w;
}
void insrt(int x){
    int pos=root;
	for(int i=31;i>=0;i--){
		int val=((x>>i)&1);
		if(!trie[pos].son[val]) trie[pos].son[val]=++tot;
		pos=trie[pos].son[val];
	}
}
void query(int x){
	int res=0,pos=root;
	for(int i=31;i>=0;i--){
		int val=((x>>i)&1);
		if(trie[pos].son[val^1]){
			pos=trie[pos].son[val^1];
			res|=(1<<i);
		}else{
			pos=trie[pos].son[val];
		}
	}
	ans=max(ans,res);
}
void add(int u,int v,int val){
	edge[++cnt].to=v;
	edge[cnt].pre=head[u];
	edge[cnt].val=val;
	head[u]=cnt;
}
void dfs(int root,int u){
	insrt(dis[u]);query(dis[u]);
	for(int i=head[u];i;i=edge[i].pre){
		int v=edge[i].to;
		if(v==root) continue;
		dis[v]=dis[u]^edge[i].val;
		dfs(u,v);
	}
}
signed main(){
	n=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read(),w=read();
		add(u,v,w);add(v,u,w);
	}
	dfs(0,1);
	printf("%lld\n",ans);
	return 0;
}

三. 可持久化的 \(\operatorname{Trie}\)

如果您还不会主席树,请移步

主席树

好的,然后可持久化的 \(\operatorname{Trie}\) 就比较显然了
那么

image
image
image
image
我们从第 \(i\) 个根节点开始遍历,可以扫到第 \(1\sim i\) 次加入的字符串

一道比较经典的例题

P4735 最大异或和

直接维护前缀和建 \(\operatorname{Trie}\) 树即可

点击查看代码
#include<cstdio>
#include<cstring>
#include<string>
#define int long long
#define WR WinterRain 
using namespace std;
const int WR=20001000;
struct Trie{
    int son[2];
}trie[WR];
int n,m;
int root[WR],rt,tot;
int sze[WR];
int read(){
	int s=0,w=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		s=(s<<3)+(s<<1)+ch-48;
		ch=getchar();
	}
	return s*w;
}
void insrt(int x){
    int pos=root[rt];
    root[++rt]=++tot;
    for(int i=31;i>=0;i--){
        int val=(x>>i)&1;
        sze[tot]=sze[pos]+1;//让长度增加
        trie[tot].son[val]=tot+1;
        trie[tot].son[val^1]=trie[pos].son[val^1];//继承上一个节点的子树信息
        pos=trie[pos].son[val];
        tot++;
    }
    sze[tot]=sze[pos]+1;
}
int query(int l,int r,int x){
    int lson=root[l],rson=root[r],res=0;
	for(int i=31;i>=0;i--){
        int val=(x>>i)&1;
        if(sze[trie[rson].son[val^1]]-sze[trie[lson].son[val^1]]>0){
            lson=trie[lson].son[val^1];
            rson=trie[rson].son[val^1];
            res|=(1<<i);
        }else{
            lson=trie[lson].son[val];
            rson=trie[rson].son[val];
        }
	}
    return res;
}
signed main(){
	n=read(),m=read();
    int sum=0;
    insrt(0);
    for(int i=1;i<=n;i++){
        int x=read();
        sum^=x;
        insrt(sum);
    }
    while(m--){
        char opt[10];
        scanf("%s",opt);
        if(opt[0]=='A'){
            int x=read();
            sum^=x;
            insrt(sum);
        }else{
            int l=read(),r=read(),x=read();
            printf("%lld\n",query(l-1,r,x^sum));
        }
    }
	return 0;
}
posted @ 2022-09-17 19:10  冬天丶的雨  阅读(40)  评论(0编辑  收藏  举报
Live2D