Trie树原理、模板及例题(更新中)

本文深入解析Trie树的数据结构原理,通过多个实战案例,包括字符串匹配、数字操作及区间查询等,展示Trie树在算法设计中的应用。特别介绍了Trie树在欧拉图判定、T9键盘输入预测、区间异或查询等问题上的高效解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Trie树原理视频讲解(油管)
模板及例题

模板

const int maxn =2e6+5;
int tree[maxn][30];
bool flagg[maxn];
int tot;
void insert_(char *str)
{
   int  len=strlen(str);
   int root=0;
   for(int i=0;i<len;i++)
   {
       int id=str[i]-'0';
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flagg[root]=true;
}
bool find_(char *str)
{
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'0';
        if(!tree[root][id]) return false;
        root=tree[root][id];
    }
    return true;
}
void init()
{
    for(int i=0;i<=tot;i++)
    {
       flagg[i]=false;
       for(int j=0;j<26;j++)
           tree[i][j]=0;
    }
   tot=0;
}

例题
POJ-2513 Colored Sticks
将一种颜色看成一块陆地,一根木棍看作一座桥(两块陆地之间可以有多座桥),求是否存在一条通路使得经过每座桥一次且仅一次。因此这道题变成欧拉图的判定,以一种颜色为陆地,每根木棍看作一座桥,使得两块陆地的度数分别加1。
在判定颜色时,由于木棍数量较多,若使用STL中的map会超时,因此应改用Trie树。
判定欧拉图需使用并查集检查图是否连通。因此这道题需用到并查集+Trie树

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<vector>
#include<map>
#include<set>
#define INF 10000000000000

using namespace std;

//字典树: 
const int maxn =2e6+5;
int tree[maxn][30];
int flagg[maxn];
int tot;
int color=0;
int insert_(char *str)
{
   	int  len=strlen(str);
	int root=0;
	for(int i=0;i<len;i++)
	{
	    int id=str[i]-'0';
	    if(!tree[root][id]) tree[root][id]=++tot;
	    root=tree[root][id];
	}
	if(!flagg[root]){
		color++;
		flagg[root]=color;
	}
	return flagg[root];
}
//并查集模板:
const int mn=5e5+10;
int par[mn],Rank[mn];
void init(){
	for(int i=0;i<=mn;i++)
	{
		par[i]=i;
	}
}

int find(int x)
{
	return x==par[x]?x:par[x]=find(par[x]);
}

void unite(int x,int y)
{
	x=find(x);y=find(y);
	if(Rank[x]<Rank[y])
	{
		par[x]=y;
	}else{
		par[y]=x;
		if(Rank[x]==Rank[y]) Rank[x]++;
	}
}

char s1[12],s2[12];
int cnt[mn];
int main()
{
	init();
	while(~scanf("%s%s",s1,s2))
	{
		int color1=insert_(s1),color2=insert_(s2);
		unite(color1,color2);
		cnt[color1]++;
		cnt[color2]++;
	}
	int Cnt=0;
	bool res=true;
	int cmp=find(1);
	for(int i=1;i<=color;i++)
	{
		if(find(i)!=cmp){
			res=false;
			break;
		}
		if(cnt[i]%2)Cnt++;
		if(Cnt>2)break;
	}
	if(Cnt!=0&&Cnt!=2)res=false;
	if(res){
		printf("Possible\n");
	}else{
		printf("Impossible\n");
	}
	return 0;
} 

POJ-1451 T9
注意权值是加在原来的权值上,而不是覆盖原来的权值。如果是新权值覆盖旧权值,则不需要用到字典树,只需要建立一个前缀与权值的映射即可。如果是权值加到原来的权值上,则需要使用字典树来记录各个节点的权值。
代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
#include<queue>
#include<vector>
#include<map>
#include<set>
#define INF 10000000000000

using namespace std;

const int mn=2e5+10;
const double esp=1e-9;
const int mod=1e9+7;

map<string,string> f;
map<string,int>cmp;
map<char,char>trans;
const int maxn =2e6+5;
int tree[maxn][30];
int sum[maxn];
bool flagg[maxn];
int tot;

void insert_(char *str,int P)
{
   	int len=strlen(str);
   	int root=0;
   	string num="";
   	string tmp="";
   	for(int i=0;i<len;i++)
   	{
       	num+=trans[str[i]];
       	tmp+=str[i];
       	int id=str[i]-'a';
       	if(!tree[root][id]) tree[root][id]=++tot;
       	root=tree[root][id];
       	sum[root]+=P;
       	if(cmp[num]<sum[root]){
       		cmp[num]=sum[root];
       		f[num]=tmp;
       	}else if(cmp[num]==sum[root]){
       		if(f[num]>tmp) f[num]=tmp;
       	}
       	
   	}
   	flagg[root]=true;
}
void init()
{
	f.clear();
	cmp.clear();
    for(int i=0;i<=tot;i++)
    {
       flagg[i]=false;
       sum[i]=0;
       for(int j=0;j<26;j++)
       {
       		tree[i][j]=0;
       }
           
    }
   tot=0;
}

int main()
{
	trans['a']=trans['b']=trans['c']='2';//建立字母与数字的映射
	trans['d']=trans['e']=trans['f']='3';
	trans['g']=trans['h']=trans['i']='4';
	trans['j']=trans['k']=trans['l']='5';
	trans['m']=trans['n']=trans['o']='6';
	trans['p']=trans['q']=trans['r']=trans['s']='7';
	trans['t']=trans['u']=trans['v']='8';
	trans['w']=trans['x']=trans['y']=trans['z']='9';
	int t;
	cin>>t;
	for(int Case=1;Case<=t;Case++)
	{
		init();
		int n;
		scanf("%d",&n);
		char str[105];
		int P;
		while(n--)
		{
			scanf("%s",str);
			scanf("%d",&P);
			insert_(str,P);
		}
		int m;
		scanf("%d",&m);
		printf("Scenario #%d:\n",Case);
		while(m--)
		{
			char s[105];
			scanf("%s",s);
			string S="";
			for(int i=0;i<strlen(s)-1;i++)
			{
				S+=s[i];
				if(cmp[S]==0){
					printf("MANUALLY\n");
				}else{
					printf("%s\n",f[S].c_str());
				}
			}
			printf("\n");
		}
		printf("\n");
	}
	return 0;
}

HDU 4825
给一个数,取集合中的一个数与之求异或,问取哪个数异或后最大。
字典树与XOR问题的结合。
如图,建立一个二叉树,将一个数转化为二进制,从高位到低位将数位放于树中,树的一个叶子节点代表一个数。高度度为n树最多能表示 2n+1-1 个正整数。
在这里插入图片描述
按题目要求,建树则将十进制的数转化为二进制,从高位到低位将数字存进树中。查询的优先查找与查询数数位不同的节点,查到叶子节点即为所求数字。
代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>

typedef long long LL;

using namespace std;

const int mn=1e5+10;
int tree[mn*35][2];
int flagg[mn*35];
int tot;
void insert_(int x)
{
   int root=0;
   for(int i=31;i>=0;i--)
   {
       int id=(x>>i)&1;//和1做与运算后数只剩第一位(例:100 & 001 = 000 ; 101 & 001 = 001)
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
   }
   flagg[root]=x;
}
int find_(int x)
{
    int root=0;
    for(int i=31;i>=0;i--)
    {
        int id=(x>>i)&1;
        if(tree[root][id^1]) root=tree[root][id^1];
        else root=tree[root][id];
    }
    return flagg[root];
}
void init()//最后清空,节省时间
{
    for(int i=0;i<=tot;i++)
    {
       flagg[i]=0;
       for(int j=0;j<2;j++)
           tree[i][j]=0;
    }
   tot=0;//RE有可能是这里的问题
}
int main()
{
	int t;
	cin>>t;
	for(int C=1;C<=t;C++)
	{
		init();
		int n,m;
		scanf("%d%d",&n,&m);
		while(n--)
		{
			int a;
			scanf("%d",&a);
			insert_(a);
		}
		printf("Case #%d:\n",C);
		while(m--)
		{
			int a;
			scanf("%d",&a);
			int res=find_(a);
			printf("%d\n",res);
		}
	}
	return 0;
}

Codeforces 665E
求区间异或大于k的区间数。
对区间做前缀异或s[ i ]=s[1] ^ s[2] ^ s[3] ^ … ^ s[ i ],那么区间( L,R ]的异或和为s[ R ] ^ s[ L ]
参考题解
代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>

typedef long long LL;

using namespace std;

const int mn=1e6+10;
int tree[mn*32][2];
LL flagg[mn*32];
int tot;
int n,k;
void insert_(int x)
{
   int root=0;
   for(int i=31;i>=0;i--)
   {
       int id=(x>>i)&1;//和1做与运算后数只剩第一位(例:100 & 001 = 000 ; 101 & 001 = 001)
       if(!tree[root][id]) tree[root][id]=++tot;
       root=tree[root][id];
       flagg[root]++;
   }
}
LL find_(int x)
{
	LL sum=0;
    int root=0;
    for(int i=31;i>=0;i--)
    {
        int id=(x>>i)&1,K=(k>>i)&1;
        if(!K){
        	sum+=flagg[tree[root][id^1]];
        	root=tree[root][id];
        }else{
        	root=tree[root][id^1];
        }
        if(!root)return sum;
    }
    return sum+flagg[root];
}

int main()
{
	cin>>n>>k;
	insert_(0);
	LL tmp=0,ans=0;
	while(n--)
	{
		int a;
		cin>>a;
		tmp^=a;
		ans+=find_(tmp);
		insert_(tmp);
	}
	cout<<ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值