字典树 总结

近期一直再写 字典树 ,拉了一套题,今天才写完,总结一下吧 ,其实这是一个比较简单的数据结构吧。

讲一下原理吧 ,这个字典树 ,就是将字符串的前缀放到一颗树上,有共同前缀的就在一个节点上 ,直接上图可能会比较容易去理解


网上借用的一张图,写法 

,写法那也很简单了,就是首先会有一个总的根节点,之后开始建树,如果当前结点下面有这个点的节点,就接着往下走,如果没有就新建一个节点,之后一直往下走就好了。还有就是 字典树的写法有两种,一种是数组形式,一种是指针开点形式,我这里还是推荐用数组吧,因为我听说 以后写ac自动机用指针有可能会MLE。

下面说说我碰到的几个算是比较典型的题吧

1.

求前缀和问题

  hdu 1251 : 就是给你一串单词,让你建一个字典,之后给你串 查询,然后让你求出这一串查询在字典中的前缀数量。

思路:  这道题就是最简单的 字典树模板题,具体做法看完字典树就可以想到了,就是先建一颗字典树,之后在每个节点前面附带这一个前缀和就好了。上代码:

//数组版本的
#include <stdio.h>
#include <string.h>
#include <algorithm>
const int maxn = 1000005;
using namespace std;

int nex[maxn][26];
int val[maxn],sz=1;
void init()
{
	memset(nex[0],0,sizeof(nex[0]));
	memset(val,0,sizeof(val));
}
void insert(char str[])
{
	int len=strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'a';
		if(nex[u][c]==0)
		{
			nex[u][c]=sz++;
		}
		u=nex[u][c];
		val[u]++;
	}
}
int query(char str[])
{
	int len=strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'a';
		if(nex[u][c]==0)
		{
			return 0;
		}
		u=nex[u][c];
	}
	return val[u];
}
int main()
{
	char ch[11];
	init();
	while(gets(ch)&&strcmp(ch,""))
	{
		insert(ch);
	}

	while(scanf("%s",ch)!=EOF)
	{
		printf("%d\n",query(ch));
	}
}


//指针版本的
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
struct node
{
	node *nex[26];
	int count;
	node()
	{
		memset(nex,0,sizeof(nex));
		count=0;
	}
}root;
void insert(char str[])
{
	node *p=&root;
	for(int i=0;str[i];i++)
	{
		if(p->nex[str[i]-'a']==NULL)
		{
			p->nex[str[i]-'a']=new node;
		}
		p=p->nex[str[i]-'a'];
		p->count++;
	}
}
int serch(char str[])
{
	node *p=&root;
	for(int i=0;str[i];i++)
	{
		if(p->nex[str[i]-'a']==NULL)
		{
			return 0;
		}
		p=p->nex[str[i]-'a'];
	}
	return p->count;
}
int main()
{
	char ch[10000],sh[10000];
	while(gets(ch)&&strcmp(ch,"")!=0)
	{
		insert(ch);
	}
	while(cin>>sh)
	{
		cout<<serch(sh)<<endl;
	}
}


上面是指针,下面是数组,还是有差别的 吧。

hdu 1671:这道题就是就是给你一组电话 问你这组电话中有没有出现过完全前缀的 比如说 91123   911就不行.

思路:  也很简单吧,就是当你建完字典树后,看你下面那个节点是否有节点,如果有节点就表示你当前建立的这个串是一个完全前缀就好了 上代码吧:




#include <stdio.h>
#include <string.h>
#include <iostream> 
const int maxn = 1e5+10;
using namespace std;
char ch[maxn][15];
int nex[maxn][11],sz,flag;
void init()
{
	flag=0;
	sz=1;
	memset(nex,0,sizeof(nex));
}
void insert(char str[])
{
	int len=strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'0';
		if(!nex[u][c])
		{
			nex[u][c]=sz++;
		}
		u=nex[u][c];
	}
}
void query(char str[])
{
	int len = strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'0';
		if(!nex[u][c])
		{
			return ;
		}
		u=nex[u][c];
	//	cout<<"u==="<<u<<"  c=="<<c<<endl;
	}
	for(int i=0;i<11;i++)//如果当前节点下面还有节点,就表明这是一个完全前缀
	{
		if(nex[u][i])
		{
		//	puts("--------------");
			flag=1;
		}
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		int n;
		init();
		scanf("%d",&n);
		for(int i=0;i<n;i++)
		{
			scanf("%s",ch[i]);
			insert(ch[i]);
		}
		for(int i=0;i<n;i++)
		{
			query(ch[i]);
			if(flag)
			{
				break;
			}
		}
		if(flag)
		{
			puts("NO");
		}
		else
		{
			puts("YES");
		}
	}
}


2. 转译问题:

这种问题应该是最简单了,也不经常用 。。。因为map一下就好了。字典树的写法就是 :在当前串的最后一个节点下面标上转译的串就好了

poj 2503 最裸的题 。。。不多说 ,直接上代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;
int sz=1;
char NEW[25],OLD[25];
char str[10002];
struct trie
{
	char trans[23];
	int next[26];
	int val;
};
trie node[300001];
void insert(char str[],char trans[])
{
	int len=strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'a';
		if(!node[u].next[c])
		{
		//	memset(node[u].next,0,sizeof(node[u].next));
		//	memset(node[u].trans,0,sizeof(node[u].trans));
			node[u].val=0;
			node[u].next[c]=sz++;
		//	cout<<str[i]<<"    "<<node[u].next[c]<<"u=="<<u<<"   c=="<<c<<endl;
		}
		u=node[u].next[c];
	}
	strcpy(node[u].trans,trans);
	node[u].val++;
}
void find(char str[])
{
	int u=0;
	int len=strlen(str);
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'a';
//		cout<<"u==="<<u<<"  c=="<<c<<endl;
		if(!node[u].next[c])
		{
			puts("eh");
			return ;
		}
		u=node[u].next[c];
	}
	if(node[u].val)
	{
		cout<<node[u].trans<<endl;
	}
	else 
	{
		puts("eh");
	}
}
int main()
{
	memset(node,0,sizeof(node));
	while(gets(str))  
    {
        if(strlen(str)==0)  
            break;  
        sscanf(str,"%s %s",OLD,NEW);
        insert(NEW,OLD);
    }
    while(scanf("%s",str)!=EOF)
    {
    //	cout<<str<<endl;
    	find(str);
	}
	return 0;
}

字典树:   map:

差别还是挺大的吧 差不多5倍。

hdu 1075:其实也是裸题,就是处理起来比较麻烦,题意是 给你一组单词,然后和他的翻译,之后给你一句话,让你翻译成一句话,不过有标点符号和空格。

思路: 这道题比较麻烦的地方就是 一句话,有空格,我们就需要把这个句话拆开,是标点符号的就还是标点符号,不是标点符号了就放到字典树里面去比较好了,我吧话拆成单词这里写的很丑 ,,所以其实并没有比map快多少,,,上代码吧:

//g++ 会T
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;

const int max_n = 26;
const int max_l = 1e6+10;
int sz;

struct trie
{
	int nex[26];
	char trans[max_n];
	int val;
}node[max_l];

char NEW[max_n],OLD[max_l],str[max_l];

void init()
{
	memset(node,0,sizeof(node));
	sz=1;
}
void insert(char str[],char trans[])
{
	int len=strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'a';
		if(!node[u].nex[c])
		{
			node[u].nex[c]=sz++;
		}
		u=node[u].nex[c];
	}
	node[u].val++;
	strcpy(node[u].trans,trans);
}

void query(char str[])
{
	int len=strlen(str);
	int u=0;
	for(int i=0;i<len;i++)
	{
		int c=str[i]-'a';
		if(!node[u].nex[c])
		{
			printf("%s",str);
			return ;
		}
		u=node[u].nex[c];
	}
	if(node[u].val)
	{
		printf("%s",node[u].trans);
	}
	else
	{
		printf("%s",str);
		return ;
	}
}
int main()
{
	init();
	while(scanf("%s",NEW)!=EOF)
	{
		if(!strcmp(NEW,"START")) continue;
		if(!strcmp(NEW,"END")) break;
		scanf("%s",OLD);
		insert(OLD,NEW);
	}
	char c;
	char te[max_l];
	getchar();
	int cnt=0;//一个一个去比对,,, 很丑 。。。 
	while(c=getchar())
	{
		if(c>='A'&&c<='Z')
		{
			getchar();
			if(c=='E') break;
			else continue; 
		}
		if(isalpha(c))
		{
			te[cnt]=c;
			cnt++;
		}
		else 
		{
			if(cnt)
			{
				query(te);
			}
			memset(te,0,sizeof(te));
			cnt=0;
			printf("%c",c);
		}
	}
}

3,字典树求异或值最大:

这个东西还是很强的,因为我们知道 异或的性质是 两个一样的取0,两个不用的就取1,那么我们可以先进一个 层数为31的字典树 (不爆long long ),然后建完之后在从高位到低位在字典树上跑,如果当前位数是0那么就找1,如果是1就找0,如果我们找不到了那就放弃当前位置,接着往下找,这样找下去我们得到的异或和就是最大的。
POJ 3764  这道题是给你一颗树,让你在树上找到一条异或值最大的路

思路: 我们都知道异或还有一个性质就是 (A^B) ^(B^C) =A^C,所以我们可以首先建立一个根节点,然后让所有节点都^根节点,之后在把权值的01串放到字典树里 跑一下就好了上代码把:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#define MAXN 212222
#define MAXM 6122222
using namespace std;
int head[MAXN],vis[MAXN],Xor[MAXN],cnt;
struct node
{
	int next,to,value;
}edg[MAXN * 2<<1];
void add(int u,int v,int w)
{
	edg[cnt].value=w;
	edg[cnt].to=v;
	edg[cnt].next=head[u];
	head[u]=cnt++;
}
void dfs(int u,int w)
{
	Xor[u]=w;
	vis[u]=1;
	for(int i=head[u];i!=-1;i=edg[i].next)
	{
		int v=edg[i].to;
		if(vis[v]) continue;
	//	printf("~~~~~%d   \n",edg[i].value);
		dfs(v,edg[i].value^w);
	}
	return ;
}
struct trie
{
	int nex[MAXM][2];
	int sz;
	void init()
	{
		sz=1;
		memset(nex,0,sizeof(nex));
	}
	void insert(int num)
	{
		int u=0;
		for(int i=30;i>=0;i--)
		{
			int a;
			if(((1<<i)&num))
			{
				a=1;
			}
			else  a=0;
			if(!nex[u][a])
			{
				nex[u][a]=sz++;
			}
			u=nex[u][a];
		}
	}
	int quert(int num)
	{
		int u=0;
		int ans=0;
		for(int i=30;i>=0;i--)
		{
			int ret=(1<<i);
			int a=((1<<i)&num);
			if(a)
			{
				a=0;
			}
			else 
			{
				a=1;
			}
			if(nex[u][a])
			{
				ans=ans|ret;
			//	cout<<ans<<endl;
				u=nex[u][a];
			}
			else 
			{
				u=nex[u][!a];
			}
		}
		return ans;
	}
}R;
int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		R.init();
		memset(vis,0,sizeof(vis));
		memset(head,-1,sizeof(head));
		cnt=0;
		int a,b,c;
		for(int i=1;i<n;i++)
		{
			scanf("%d%d%d",&a,&b,&c);
			a++,b++;
			add(a,b,c);
			add(b,a,c);
		}
		dfs(1,0);
		int ans=0;
		for(int i=1;i<=n;i++)
		{
		//	printf("%d   ",Xor[i]);
			ans=max(ans,R.quert(Xor[i]));
			R.insert(Xor[i]);
			
		}
		printf("%d\n",ans);
	}
}

4.瞎搞题:

这类就是字典树瞎搞。。都是些比较难的题把。。

HDU 1298 : 题意: 手机的九键,给你一个单词频率然后让你,每按一个键就输出当前频率最高的单词。

思路: 也是加一个带有权值的字典树,之后每按一个就在字典树上跑DFS ,找到权值最大的就好了,小细节很多,,,



POJ 2503 题意:给你几根棍子,首尾有单词,然后首尾单词相同的可以连在一起,问你能否练成一个圈。

思路:这道题如果首尾不是单词的话,那就是一个裸的欧拉回路的题了,,这道题也是,就是利用字典树,给当前颜色建立一个编号就好了,,,也是很简单,我没想到的是竟然可以用并查集来检验图是否联通,,这都没想到真是菜。。。然后说一下欧拉回路,在无项图中如和判断一个图是欧拉回路,解法就是,首先这个图要联通,接下来就是每个节点的度都要是偶数,或者有且只有两个是奇数度的点:

上代码:

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
const int maxn = 521521;
using namespace std;
struct node 
{
	int nex[26];
	int id;
	int flag;
}edg[maxn];
int sz=1,cnt=0,p[maxn];
int flag[maxn];
void init()
{
	sz=1;
	cnt=0;
	memset(edg,0,sizeof(edg));
	memset(flag,0,sizeof(flag));
	for(int i=0;i<maxn;i++)
	{
		p[i]=i;
	}
}
int getf(int x) 
{
	if (p[x] == x) return x;
	else return p[x] = getf(p[x]);
}
int insert(char str[])
{
	int u=0;
	int len = strlen(str);
	for(int i=0;i<len;i++)
	{
		int t=str[i]-'a';//这里还写了个'0'  一直在debug。。。。 
	//	printf("nex===%d  %s\n",edg[u].nex[t],str);
		if(!edg[u].nex[t])
		{
			edg[u].nex[t]=sz++;
		}
		u=edg[u].nex[t];
	}
	if(edg[u].flag==0)
	{
		edg[u].flag=1;
		edg[u].id=cnt++;
	}
	return edg[u].id;
}
int main()
{
	char a[11],b[11];
	init();
	while(scanf("%s%s",a,b)!=EOF)
	{
		int x=insert(a);
		int y=insert(b);
	//	cout<<"x== "<<x<<"    y== "<<y<<endl;
		flag[x]++;
		flag[y]++;
		int dx=getf(x);
		int dy=getf(y);
		if(p[dx]!=dy)
		{
			p[dx]=dy;
		}
	}
	int count=0,pp=0;
	for(int i=0;i<cnt;i++)
	{
		if(p[i]==i)
		{
			count++;
		}
	}
//	cout<<count<<endl;
	int flag1=0;
	for(int i=0;i<cnt;i++)
	{
		if(flag[i]%2)
		{
			flag1++;
		}
	}
	if(count<=1) //比较坑的是 ,没有点也是可以连的 。。我这里写了一个 ==1 worng 到怀疑人生。。。 
	{
		if(flag1==0||flag1==2)
		{
			puts("Possible");
		}
		else 
		{
			puts("Impossible");
		}
	}
	else
	{
		puts("Impossible");
	}
	
}

BZOJ 1819 :中文题意。

思路 :本来不是特别会写,,,但是一看时间 10s,,果断暴力枚举匹配串的每一个情况一个一个的拉到字典树上面去比较。。其实不是特别难就是有些小细节比较坑,上代码把:

#include <stdio.h>
#include <string.h>
 
 
int nex[210000][26],end[200010],flag=0;
int vis[200010];
int sz;
void insert(char ch[])
{
    int len = strlen(ch);
    int u = 0,t;
    for(int i = 0 ; i < len; i++)
    {
        t = ch[i]-'a';
        if(!nex[u][t])
        {
            nex[u][t]=sz++;
        }
        u = nex[u][t];
    }
    end[u] = 1;
}
int query(char ch [])
{
    int len = strlen(ch);
    int u = 0,t;
    for(int i = 0;i < len; i++)
    {
        t=ch[i]-'a';
        if(!nex[u][t])
        {
            return 0;
        }
        u=nex[u][t];
    }
    if(end[u])
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
int judge(char TT[])
{
    int u=0,t;
     int len = strlen (TT);
     for(int i = 0 ; i < len ;i++)
     {
        t = TT[i]-'a';
        if(!nex[u][t])
        {
            return 0;   
        }
        u = nex[u][t];
     }
    if(!end[u])
        return 0;
    if(vis[u]!=flag)
    {
    //  printf("%d   %d\n",vis[u],flag);
        return vis[u]=flag,1;
    }
    else return 0;
}
int main()
{
    memset(nex,0,sizeof(nex));
    memset(end,0,sizeof(end));
    memset(vis,0,sizeof(vis));
    int n,m;char ch[25],TT[25];
    scanf("%d%d",&n,&m);
    sz=1;
    int pp = 0;
    for(int i=0;i<n;i++)
    {
        scanf("%s",ch);
        int len = strlen(ch);
        insert(ch);
    }
    //printf("%d\n",pp);
    while(m--)
    {
        scanf("%s",ch);
        int t=query(ch);
        if(t)
        {
            puts("-1");
        }
        else
        {
            flag++;
            int len = strlen(ch);
            int cnt = 0,ans = 0;
            int o;
            memset(TT,0,sizeof(TT));
            if(len>1)
            {
                for(int i= 0 ;i < len ; i++)
                {
                    cnt = 0;
                    for(int j = 0;j < i; j++)
                    {
                        TT[cnt]=ch[j];
                        cnt++;
                    }
                    for(int j = i+1; j<len; j++)
                    {
                        TT[cnt] = ch[j];
                        cnt++;
                    }
                    o = judge(TT);
                //  printf("%s   \n",TT);
                    ans+=o;
                }
            }
        //  puts("``````````````````````````````````````````````````````````````");
            memset(TT,0,sizeof(TT));
            for(int i = 0; i <=len ;i++)//我这里没写 等于号 然后 下面写的是 j<= i,j=i+1,这样写会使得我们无法填第一个字母前面,,也是改bug改到吐。。。 
            {
                for (int k = 0 ;k < 26; k ++)
                {
                    cnt = 0;
                    for (int j = 0 ;j<i ;j++)
                    {
                        TT[cnt]=ch[j];
                        cnt ++;
                    }
                    TT[cnt]=k+'a';
                    cnt++;
                    for (int j = i ; j < len; j ++)
                    {
                        TT[cnt]=ch[j];
                        cnt++;
                    }   
                //  printf("%s  \n",TT);
                    o=judge(TT);
                 
                    ans+=o;
                }
            }
        //  puts("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            memset(TT,0,sizeof(TT));
            strcpy(TT,ch);
            for (int i =0 ;i < len ; i++ )
            {
                for (int j =0 ;j < 26;j++)
                {
                    TT[i] = j+'a';
                //  printf("%s    \n",TT);
                    o=judge(TT);
                    ans+=o;
                }
                TT[i]=ch[i];
            }
            printf("%d\n",ans);
         
        }
    }
}
 
/*
4 100
 
abcd
 
abcde
 
aabc
 
abced
 
abcd
 
abc
 
abcdd
*/ 

BZOJ 1212  题意中文 不解释了

思路:  还没相通,,,需要利用一些 DP思想,而且疯狂T ,原来以为是BZOJ 的垃圾测评鸡有问题(??菜怪测评鸡??) 于是拉到洛谷上 果断最后两个点T掉了,,发现自己代码写得好搓啊 ,,一样思路别人就过我就T,不懂为什么 。。。先不上代码把。。。







  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值