字典树(trie):或名前缀树,哈希树的变种,大多题目(非水题)与哈希树套用求解。矮+胖为其显著特征,以空间换时间的典例。

通过利用字符串的公共前缀可实现字符串的快速查询。

板子如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;

int tot=0;
int tree[maxn][30];
int flag[maxn];

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;
        root=tree[root][id];
    }
    flag[root]=1;
}

bool fid(char *s)
{
    int len=strlen(s);
    int root=0;
    for(int i=0;s[i];i++)
    {
        int id=s[i]-'a';
        if(tree[root][id]==0)
        {
            return 0;
        }
        root=tree[root][id];
    }
    return flag[root];
}
/*
删除操作:
    三种可能:
    1、没找到该单词
    2、找到叶节点时、叶节点的cnt标志清零,代表该节点不是叶节点
    3、当前节点没有其他的孩子节点时,可直接删除该节点
*/
void del(char *str,int word)
{
    int len=strlen(str);
    int root=0;
    if(word==0)//未找到
    {
        return ;
    }
    for(int i=0;i<len;i++)
    {
        int id=str[i]-'a';
        if(tree[root][id]==0)//没有子节点
            return;
        sum[root]-=cnt;
        root=tree[root][id];
    }
    sum[root]=0;
    for(int i=0;i<26;i++)
    {
        tree[root][id]=0;
    }
}

struct Node{
    int sum;//前缀
    int next[26];//子节点
    void init(){
        sum=0;
        memset(next,-1,sizeof next);
    }
}tire[N];

int tot;
void add(char *str){
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++){
        int x=str[i]-'a';
        if(tire[root].next[x]==-1)
            tire[root].next[x]=tot++;
        root=tire[root].next[x];
        tire[root].sum++;
    }
}
int fid(char *str){
    int len=strlen(str);
    int root=0;
    for(int i=0;i<len;i++){
        int x=str[i]-'a';
        if(tire[root].next[x]==-1)
            return 0;
        root=tire[root].next[x];
    }
    return tire[root].sum;
}
void del(char *str,int word){
    int len=strlen(str);
    int root=0;
    if(word<0)
        return;
    for(int i=0;i<len;i++){
        int x=str[i]-'a';
        if(tire[root].next[x]==-1)
            return;
        tire[root].sum-=word;
        root=tire[root].next[x];
    }
    tire[root].sum=0;

    for(int i=0;i<26;i++)
        tire[root].next[i]=-1;
}

int main(){
    tot=1;
    for(int i=0;i<N;i++)
        tire[i].init();

    int t;
    scanf("%d",&t);
    while(t--){
        char str[10],word[35];
        scanf("%s%s",str,word);
        if(str[0]=='i')//插入
            add(word);
        else if(str[0]=='d')//删除
            del(word,fid(word));
        else{//查询
            if(fid(word)>0)
                printf("Yes\n");
            else
                printf("No\n");
        }
    }
    return 0;
}


//前缀出现次数
int trie[400001][26],tot;
int sum[400001];
void add(char *s){
    int root=0;
    int len=strlen(s);
    for(int i=0;i<len;i++){
        int id=s[i]-'a';
        if(!trie[root][id])
            trie[root][id]=++tot;
        sum[trie[root][id]]++;//前缀保存
        root=trie[root][id];
    }
}
int fid(char *s){
    int root=0;
    int len=strlen(s);
    for(int i=0;i<len;i++){//root经过循环后变成前缀最后一个字母所在位置
        int id=s[i]-'a';
        if(!trie[root][id])
            return 0;
        root=trie[root][id];
    }
    return sum[root];
}
int main(){
    int n,m;
    char s[11];

    scanf("%d",&n);//插入单词个数
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        add(s);
    }

    scanf("%d",&m);//查询次数
    for(int i=1;i<=m;i++){
        scanf("%s",s);
        printf("%d\n",add(s));
    }
}


//单词|前缀是否出现
int tot=1;
int trie[N][26];//trie[rt][x]=tot,root是上个节点编号,x是字母,tot是下个节点编号
//bool vis[N];//查询整个单词用
void add(char *s,int root){
    for(int i=0;s[i];i++){
        int x=s[i]-'a';
        if(trie[root][x]==0)//现在插入的字母在之前同一节点处未出现过
            trie[root][x]=++tot;//字母插入一个新的位置,否则不做处理
        root=trie[root][x];//为下个字母的插入做准备
    }
    //vis[root]=true;//标志该单词末位字母的尾结点,在查询整个单词时用
}
bool add(char *s,int root){
    for(int i=0;s[i];i++){
        int x=s[i]-'a';
        if(trie[rt][x]==0)
            return false;//以rt为头结点的x字母不存在,返回0
        rt=trie[rt][x];//为查询下个字母做准备
    }
    return true;
    //return vis[rt];//查询整个单词时
}

int main(){
    int n,m;
    char s[22];
    tot=0;

    scanf("%d",&n);//插入单词个数
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        add(s,1);
    }

    scanf("%d",&m);//查询单词个数
    for(int i=1;i<=n;i++){
        scanf("%s",s);
        if(fid(s,1))
            printf("YES\n");
        else
            printf("NO\n");
    }
    return 0;
}

 

例题:

 

A:

 

板子题:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;//26¸ö×Öĸ
bool flag[maxn];
int sum[100007];
char a[100007];
typedef long long ll;
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;
        sum[tree[root][id]]++;
        root=tree[root][id];
    }
    //flag[root]=true;
}

ll fid(char *s)
{
    //long long res=0;
    int root=0,id,len=strlen(s);
    for(int i=0;i<len;++i)
    {
        id=s[i]-'a';
        if(!tree[root][id]) return 0;
        root=tree[root][id];
    }
    return sum[root];
    //if(flag[root]) return 1;
    /*else
        return 0;*/
}

int main()
{
    int n;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%s",a);
        add(a);
    }
    int m;
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",a);
        printf("%lld\n",fid(a));
    }

    return 0;
}

 

B:

 

思路:有趣题,可通过建双trie储存字符串拼接对比得到结果。

#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;
}

 

C:HDU-1251

 

 思路:和A类似,板子题

#include <bits/stdc++.h>

using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;//26¸ö×Öĸ
bool flag[maxn];
int sum[100007];
char a[100007];
typedef long long ll;
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;
        sum[tree[root][id]]++;
        root=tree[root][id];
    }
    //flag[root]=true;
}

ll fid(char *s)
{
    //long long res=0;
    int root=0,id,len=strlen(s);
    for(int i=0;i<len;++i)
    {
        id=s[i]-'a';
        if(!tree[root][id]) return 0;
        root=tree[root][id];
    }
    return sum[root];
    //if(flag[root]) return 1;
    /*else
        return 0;*/
}

int main()
{
    while(gets(a))
    {
        if(a[0]=='\0') break;
        add(a);
    }
    char b[maxn];
    while(scanf("%s",b)!=EOF)
    {
        printf("%lld\n",fid(b));
    }
    return 0;

}

 

 D:Immediate Decodability  HDU-1305

 

 思路:字典树的第二姿势,即对前缀的查询,稍微改改板子。若有字符串作为其他字符串的前缀,即为可消除的,也就是有一个串上非末尾阶段有flag标记。

#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 fid(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()
{
    int k=1;
    int cnt=0;

    while(scanf("%s",a[cnt])!=EOF){
        if(a[cnt][0]!='9'){
        add(a[cnt]);
        cnt++;
        }
		else{
		    bool pd=0;
            for(int i=0;i<cnt;i++){
                if(!fid(a[i])){
                    pd=1;
                    break;
                }
            }
            if(!pd)	printf("Set %d is immediately decodable\n",k++);
            else    printf("Set %d is not immediately decodable\n",k++);
            cnt=0;
		for(int i=0;i<=tot;++i){
			for(int j=0;j<10;++j){
				tree[i][j]=0;
			}
		}
		}
	}
    return 0;
}

 

 E : What Are You Talking About    HDU - 1075

用map做的,这里就不开了,到时候开map的时候会拿出来写的。

F:Phone List poj-3630

在poj上6w多KB过的,学长搬过来卡内存,就不会了。

也就gg了17发而已。

G: Shortest Prefixes    POJ - 2001

 

思路:每个每个去找最大的没有flag的地方。

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
const int maxn=2e6+5;
int tree[maxn][30],tot=0;//26¸ö×Öĸ
bool flag[maxn];
int sum[100007];
typedef long long ll;
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 fid(char *s)
{
    string ans=""; //long long res=0;
    int root=0,id,len=strlen(s);
    for(int 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[1007][30];
int main()
{
    int j=0;
    int cot=0;
    while(scanf("%s",a[j++])!=EOF)
    {
        //if(cot==12) break;
        add(a[j-1]);
        //cot++;
        //printf("1\n");
    }
    //printf("J:%d\n",j);
    for(int i=0; i<j; ++i)
    {
        //cout<<"1"<<endl;
        cout<<a[i]<<" "<<fid(a[i])<<endl;
    }
    return 0;
}

 

 

H - 全文检索   HDU - 1277

瞎搞过的,极其难看,这里就不提了。

 

还有几个快乐的题比如说poj的1816,很有意思。

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>

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 fid(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) {
			fid(s,tree[root][26],j);
		}
	}
	if(tree[root][27]) { //£¿
		fid(s,tree[root][27],pos+1);
	}
	id=s[pos]-'a';
	if(s[pos]>='a'&&s[pos]<='z'&&tree[root][id]) {
		fid(s,tree[root][id],pos+1);
	}
}
char tp[100];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;++i)
    {
        scanf("%s",tp);
        p[i]=add(tp);

    }
    while(m--!=0)
    {
        scanf("%s",tp);
        fid(tp,0,0);
        int ok=0;
        for(int i=0;i<n;++i)
        {
            if(vis[p[i]])
            {
                printf("%d ",i);
                ok=1;
            }
        }
        if(!ok) printf("Not match\n");
        else
            printf("\n");
        for(int i=0;i<n;++i) vis[p[i]]=0;
    }

    return 0;
}

 

搞懂哈希之后会继续补的。