字典树入门和例题

视频讲解戳这里

何为字典树:

如图所示:

每个字符有很多个分支,打黄色标记的就是字符串的结尾,所以这颗字典树中有哪些字符串呢,"ab","ay","ayf","c","cc","cd",其他的枝没有画全。 

如何存储:

 

顺序存储字符串:“ab”“ay”“ayf”“c”“cc”“cd……(节点编号讲究先到先得)

数组tree[i][j]:代表i节点的第j个儿子的节点编号。(获取第几个孩子可以s[i]-'a',以图中5节点举例,就是tree[0][2]=5)

数组flag[i]:true代表到该节点为一个字符串

模板代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;
bool flag[maxn];
void add(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	flag[root]=true;
}
bool find(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree[root][id])	return false;
		root=tree[root][id];
	}
	if(flag[root])	return true;
	else	return false;
}
char a[1005];
int main(){
	add(a);
	find(a);
	return 0;
}

例题归纳:

1.HDU 1251 统计难题

题目大意:统计出以某个字符串为前缀的单词数量(单词本身也是自己的前缀).

解题思路:增加一个sum数组把到一个节点 (以该节点为尾的字符串作为前缀) 的字符数存储起来。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],sum[maxn],tot=0;
void add(char *s){
	int len=strlen(s);
	int root=0;
	for(int i=0;i<len;++i){
		int id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		sum[tree[root][id]]++;
		root=tree[root][id];
	}
}
ll find(char *s){
	ll res=0;
	int root=0,len=strlen(s);
	for(int i=0;i<len;++i){
		int id=s[i]-'a';
		if(!tree[root][id])	return 0;
		root=tree[root][id];
	}
	return sum[root];
}
char tp[maxn];
int main(){
	std::ios::sync_with_stdio(0);
	while(gets(tp)){
		if(tp[0]=='\0')	break;
		add(tp);
	}
	while(scanf("%s",tp)!=EOF){
		printf("%lld\n",find(tp));
	}
	return 0;
}

2.HDU 2072 单词数

题目大意:就是统计一篇文章里不同单词的总数。

解题思路:在add的时候存储一下就行了。(不了解stringstream戳我

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30];
bool flag[maxn];
int ans=0,tot=0;
void add(string s){
	int root=0,id;
	for(int i=0;i<(int)s.size();++i){
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	if(!flag[root])	ans++;
	flag[root]=1;
}
int main(){
	std::ios::sync_with_stdio(0);
	string s,tp;
	while(getline(cin,s)){
		if(s=="#")	break;
		stringstream ss(s);
		while(ss>>tp){
			add(tp);
		}
		cout<<ans<<endl;

		ans=0;
		for(int i=0;i<=tot;++i){
			flag[i]=0;
			for(int j=0;j<30;++j){
				tree[i][j]=0;
			}
		}
		tot=0;
	}
	return 0;
}

结合set也很简单;

#include<bits/stdc++.h>
using namespace std;
int main() {
	string str1,str2;
	while(getline(cin,str1)) {
		if(str1 == "#")
			break;
		stringstream stream(str1);
		set<string> tp;
		while(stream>>str2) { 
			tp.insert(str2); 
		}
		cout<<tp.size()<<endl; 
	}
	return 0;
}

3.POJ 2001 Shortest Prefixes

题目大意:求能代表字符串的最小前缀,独一无二,没有歧义的。

解题思路:用sum[root]记录每个节点的数量,只要在find时候走到sum[root]==1的时候说明只有该字符串到过这,那么返回字符串就可以了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30];
bool flag[maxn];
int tot=0,sum[maxn];
void add(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
		sum[root]++;
	}
	flag[root]=1;
}
string find(char *s){
	int root=0,id,i,len=strlen(s);
	string ans="";
	for(i=0;i<len;++i){
		id=s[i]-'a';
		root=tree[root][id];
		ans+=s[i];
		if(sum[root]==1)	return ans;
	}
	return ans;
}
char a[1005][25];
int main(){
	int cnt=0;
	while(scanf("%s",a[cnt++])!=EOF){
		add(a[cnt-1]);
	}
	for(int i=0;i<cnt;++i){
		cout<<a[i]<<" "<<find(a[i])<<endl;
	}
	return 0;
}

4.POJ 3630 Phone List

题目大意:给你很多字符串,问你是不是任何一个字符串都不是其他字符串的前缀,输出YES或NO

解题思路:用sum[root]记录每个节点的数量,如果find一个字符串时,他走过的路上的节点sum值都大于等于2,说明他肯定是某个或者某些字符串的前缀,返回false;

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][15],sum[maxn],tot=0;
char a[10005][15];
int T,n;
void add(char *s) {
	int root=0,id,len=strlen(s);
	for(int i=0; i<len; ++i) {
		id=s[i]-'0';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
		sum[root]++;
	}
}
bool find(char *s) {
	int root=0,id,len=strlen(s),tp=0;
	for(int i=0; i<len; ++i) {
		id=s[i]-'0';
		root=tree[root][id];
		if(sum[root]>=2)	tp++;
	}
	if(tp==len)	return false;
	else	return true;
}
int main() {
	scanf("%d",&T);
	while(T--) {
		scanf("%d",&n);
		for(int i=0; i<n; ++i) {
			scanf("%s",a[i]);
			add(a[i]);
		}
		bool pd=0;
		for(int i=0;i<n;++i){
			if(!find(a[i])){
				pd=1;
				break;
			}
		}
		if(!pd)	cout<<"YES"<<endl;
		else	cout<<"NO"<<endl;
		
		for(int i=0;i<=tot;++i){
			for(int j=0;j<10;++j){
				tree[i][j]=0;
			}
		}
	}

	return 0;
}

稍难:

5.POJ 1816 Wild Words

题目大意:给你n个模板字符串和m个待匹配字符串,模板字符串中有‘?’可以匹配任意一个字符,‘*’可以匹配0个1个或者多个字符串。问待匹配字符串可以和哪些模板字符串相匹配。题解戳我

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;
bool flag[maxn],vis[maxn];
int n,m,p[maxn];
int add(char *s) {
	int root=0,id,len=strlen(s);
	for(int i=0; i<len; ++i) {
		if(s[i]=='*')	id=26;
		else if(s[i]=='?')	id=27;
		else id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	flag[root]=1;
	return root;
}
void find(char *s,int nroot,int pos) {
	int root=nroot,id,len=strlen(s);
	if(pos>len)	return;
	if(pos==len && flag[root]) {
		vis[root]=1;
	}
	if(tree[root][26]) { //有*
		for(int j=pos; j<=len; ++j) {
			find(s,tree[root][26],j);
		}
	}
	if(tree[root][27]) { //有?
		find(s,tree[root][27],pos+1);
	}
	id=s[pos]-'a';
	if(s[pos]>='a'&&s[pos]<='z'&&tree[root][id]) {
		find(s,tree[root][id],pos+1);
	}
}
char tp[50];
int main() {
	scanf("%d%d",&n,&m);
	for(int i=0; i<n; ++i) {
		scanf("%s",tp);
		p[i]=add(tp);
	}
	while(m--) {
		scanf("%s",tp);
		find(tp,0,0);
		bool flag=0;
		for(int i=0; i<n; ++i) {
			if(vis[p[i]]) {
				cout<<i<<" ";
				flag=1;
			}
		}
		if(!flag)	cout<<"Not match"<<endl;
		else	cout<<endl;
		for(int i=0; i<n; ++i)	vis[p[i]]=0;
	}
	return 0;
}




6.HDU 1247 Hat’s Words

题目大意:给你字符串,输出其中能由其他两个字符串组合而成的字符串。题解戳我

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree1[maxn][30],tree2[maxn][30];
//一正序,一反序 
bool flag1[maxn],flag2[maxn];
int vis[maxn];
int tot1=0,tot2=0;
void add1(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree1[root][id])	tree1[root][id]=++tot1;
		root=tree1[root][id];
	}
	flag1[root]=true;
} 
void add2(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		if(!tree2[root][id])	tree2[root][id]=++tot2;
		root=tree2[root][id];
	}
	flag2[root]=true;
} 
void find1(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		root=tree1[root][id];
		if(flag1[root])	vis[i]++;
	}
}
void find2(char *s){
	int root=0,id,len=strlen(s);
	for(int i=0;i<len;++i){
		id=s[i]-'a';
		root=tree2[root][id];
		if(flag2[root])	vis[len-i-2]++;
	}
}
char a[50005][100];
set<string> ans; 
int main(){
	int cnt=0;
	while(scanf("%s",a[cnt++])!=EOF){
		add1(a[cnt-1]);
		strrev(a[cnt-1]);
		add2(a[cnt-1]);
		strrev(a[cnt-1]);
	}
	
	for(int i=0;i<cnt;++i){
		int len=strlen(a[i]);
		for(int j=0;j<len;++j)	vis[j]=0;
		find1(a[i]);
		strrev(a[i]);
		find2(a[i]);			
		strrev(a[i]);
		for(int j=0;j<len;++j){
			if(vis[j]>=2){
				string tp=a[i];
				ans.insert(tp);
				break;
			}
		} 
	}
	for(auto it=ans.begin();it!=ans.end();++it){
		cout<<*it<<endl;
	}
	return 0;
}
 
 

7.POJ 2513 Colored Sticks

题目大意:给你两段有颜色的木棒,只有相同的颜色才能连接起来,问你最后给你的木棒最后能不能拼成一根长木棒。题解戳我

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
typedef long long ll;
const ll inf=0x3f3f3f3f;
const int maxn=2e6+5;
int tree[maxn][30],sum[maxn];
int vis[maxn],num[maxn];
int tot=0,cnt=0;
int f[maxn],r[maxn];
int add(char *s) {
	int root=0,id,len=strlen(s);
	for(int i=0; i<len; ++i) {
		id=s[i]-'a';
		if(!tree[root][id])	tree[root][id]=++tot;
		root=tree[root][id];
	}
	if(!vis[root])	vis[root]=++cnt;
	return vis[root];
}
void init() {
	for(int i=0; i<maxn; ++i) {
		f[i]=i;
		r[i]=0;
	}
}
int find(int x) {
	if(x=f[x])	return f[x];
	else return f[x]=find(f[x]);
}
void unite(int x,int y) {
	x=find(x);
	y=find(y);
	if(x==y)	return ;
	if(r[x]<r[y])	f[x]=y;
	else {
		f[y]=x;
		if(r[x]==r[y])	r[y]++;
	}
}
char s1[20],s2[20];
int main() {
	int tp1,tp2;
	init();
	while(scanf("%s%s",s1,s2)!=EOF) {
		tp1=add(s1);
		tp2=add(s2);
		num[tp1]++;
		num[tp2]++;
		unite(tp1,tp2);
	}
	int flag=find(1),tt=0;
	for(int i=1; i<=cnt; ++i) {
		if(num[i]%2)	tt++;
		if(find(i)!=flag) {
			cout<<"Impossible"<<endl;
			return 0;
		}
	}
	if(tt==0||tt==2)	cout<<"Possible"<<endl;
	else	cout<<"Impossible"<<endl;
	
	return 0;
}




 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值