NOIP大纲整理:(六)字符串1:trie树(字典树)

1、trie树(字典树):将字母挂在一棵树上

1.定义:

通过字符串建成一棵树,这棵树的节点个数一定是最少的。例如:4个字符串"ab","abc","bd","dda"对应的trie树如下:

其中红色节点表示存在一个字符串是以这个点结尾的。 

一个性质:在树上,两个点u,v满足u是v的祖先,那么u代表的字符串一定是v代表的字符串的前缀。

2.Trie树的插入:

可以从根节点出发,每次沿着要走的字符串往下走,若没有则建立新节点。 

假如所有字符串的长度之和为n,构建这棵trie树的时间复杂度为O(n)。

int root=1;

int cnt=1;

int p[i][j];//表示从i这个节点沿着j这个字符走,能走到哪个点,走不到就是0

char s[];//存储字符串

for(int i=1;i<=n;i++)

{

   scanf("%s",s);

    int len=strlen(s);

    int now=root;//now表示走到的当前节点,now的初始值为根节点

    for(intj=0;j<len;j++)

    {

       if(p[now][s[j]]>0){

           now=p[now][s[j]];//如果能沿着j节点往下走,直接往下走

        }

        else{

           pow[now][s[j]]=++cnt;

           now=p[now][s[j]];

        }

    }

    v[now]++;//记录now这个节点被访问的次数

}

3.Trie树的查询:

可以看出trie树中每个节点表示其中一个字符串的前缀,在做题过程中往往通过这个性质来得到较好的时间复杂度。

4.一个例题:

给定n个互不相同的串,求存在多少对数(i,j)(共n2对)满足第i个串是第j个串的前缀。 

所有串的长度之和≤500000。 

解题:根据性质,“在树上,两个点u,v满足u是v的祖先,那么u代表的字符串一定是v代表的字符串的前缀”。 

我们要满足一个串是另一个串的前缀,也就是说,在trie树上,这个串对应的位置是另一个串对应的位置的祖先。 

构建这棵trie树,然后我们枚举每个红色点,它对答案的贡献是以它为根的子树中红色节点的个数之和。这个东西可以在一开始遍历这棵树预处理出来! 

时间复杂度是线性的。

void dfs(int x)

{

    if(v[x]) sum[x]++;

    for(chari='a';i<='z';i++)

    {

        if(p[x][i])//从当前x沿着i这个字符走还能往下走

        {

           dfs(p[x][i]);//往下走

           sum[x]+=sum[p[x][i]];//累加贡献sum

        }

    }

}

dfs(1);//从根节点开始

5.USACO的某题:

给定n个串,重排字符之间的大小关系,问哪些串有可能成为字典序最小的串。 

所有字符串的长度之和<=100000。 

例如,有一个字符串,里面只有'a'~'z'这些字符,默认地,'a'是最小的,'z'是最大的。但是我们可以重新定义字符间的大小关系,比如这样:b<c<d<y<x<z,从而我们对于一些字符串,就按照我们新定义的大小关系来比较字典序大小。 

举个栗子: 

三个字符串"aab","aba","baa",总共只出现了两个字符'a'和'b',所以字符间的大小关系要么是a<b要么是a>b,假设a<b,则第一个串"aab"就是字典序最小的串;假设b<a,则第三个串"baa"就是字典序最小的串,但是对于第二个串,无论我们怎么定义字符间的大小关系,都不可能成为三个字符串中字典序最小的。 

解题:首先对这n个串构建trie树,之后对每个串,从根走向它,这个路程中遇到的所有兄弟在字典序下都比它大。 

对于每个串,从前往后,直接确定这个字符的大小关系,判断是否是字典序最小。 

以下两个串都是有可能成为字典序最小的串的: 

abcd…xyza 

abcd…xyzb 

所以有:u是v的前缀,则v一定不是字典序最小的串。 

现在问题来了:对于每个串,在什么条件下,是字典序最小的?? 

来个栗子:有一些字符串("aabc","aac","aad","ac","adb","aabb"),构造trie树如下:

对于"aabc"这个串,往下遍历: 

从深度为2的那一层,我们可以得到:a<c,a<d; 

从深度为3的那一层,我们可以得到:b<c,b<d; 

从深度为4的那一层,我们还可以得到:c<b。 

综上所述:我们得到了一堆的关系:a<c,a<d;b<c,b<d;c<b,容易看出,这些关系是存在矛盾的,所以无解->"aabc"是不可能成为字典序最小的串。 

所以要想有解,这一堆的关系必须满足两个条件:①不存在矛盾②不存在环 

总的来说,就是判断这一堆的关系是否存在拓扑序! 

最多有26×26个大小关系,而最多有100000个字符串,所以时间复杂度最大为:O(26×26×100000)。

暂时代码是转载的,以后有机会会更新,看不懂请跳过

//例:HDU 1075
#include <cstdio>
#include <string>
using namespace std;
struct trie
{
trie * next[26];
int index;
};
trie *thead;
char dic[1000000][20];
inline trie * newnode()
{
int i;
trie *t;
t=(trie*)malloc(sizeof(trie));
memset(t,0,sizeof(trie));
return t;
}
void insert(trie * s,char x[],int pos)
{
int i;
trie *t;
for(i=0; x[i] ;i++) {
if(s->next[ x[i]-'a' ] ) s=s->next[ x[i]-'a' ];
else{
t=newnode();
s->next[x[i]-'a' ]=t;
s=t;
}
}//for
s->index=pos;
}
void deltrie(trie * s)
{
int i;
for(i=0; i < 26;i++) {
if(s->next[i] ) deltrie(s->next[i]);
}
free(s);
s=NULL;
}
int find(trie *s, char x[])
{
int i;
if(x[0] == 0)return -1;
for(i=0; x[i] ;i++) {
if(s->next[ x[i]-'a' ] ) s=s->next[ x[i]-'a' ];
elsebreak;
}
if(x[i]==0) returns->index;
else return -1;
}
int main()
{
int t,n,i,j,all;
charword[20],mars[20],ch;
thead=newnode();
while(scanf("%s",word)==1)
if(word[0]=='S')break;
i=1;
while(scanf("%s",dic[i])==1&& dic[i][0]!='E') {
scanf("%s",mars);
insert(thead,mars,i);
i++;
}
all=i;
while(scanf("%s",word)==1)
if(word[0]=='S')break;
getchar(); j=0;
while(scanf("%c",&ch)==1&& ch!='E') {
if(ch>='a'&& ch<='z') {
mars[j]=ch;j++;
}
else {
mars[j]=0;
t=find( thead , mars );
j=0;
if(t>0)printf("%s",dic[t]);
else if(mars[0]!=0)printf("%s",mars);
printf("%c",ch);
}
}//while
deltrie(thead);
}

--------------------------------------------------------------------------------------------------------------------------

猜你喜欢

转载自blog.csdn.net/liusu201601/article/details/81434605