#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int MAX_SIZE = 1000100;
/**
【0.9%】SPOJ7758 Grwoing Strings 解题报告 + AC代码 + 思路 + AC自动机简短总结
——果然智商捉急的话就要WA到死才能AC啊人生来不是给打败的呢
Problem: SPOJ7758-Growing Strings
URL:http://www.spoj.pl/problems/MGLAR10/
Thanks to : lpp大神给的标程,由此fixed掉了自己一个方程的Error
Reference: LPP标程,在这里就不发出来了,没有Authority~
Knowledge Point: AC自动机
Pre-Knowledge:KMP算法,详解在
http://blog.csdn.net/c0de4fun/article/details/7845625
这里
Thought:
这道题的歌词大意大概就是,有一个农场种字符串儿。
比如说字符串a,可以长成ab,也可以长成ba。
就是说可以往左长,也可以往右长,但是不可以在中间插入字符。
现在问你最多由其中之一个字符串能长出多少个字符串儿。
这道题刚开始的时候误读题意导致我日了一星期的后缀数组,后来发现不是那么回事儿我就哭了。
关于后缀数组也会有软文一篇,避免后人走我那一星期的地狱之旅。
好了不废话,从0开始学AC自动机,直到干掉这道题。
AC自动机,网上说的神乎其技,大概就是在Trie树(又叫字典树)上进行KMP的这么一个过程。
KMP算法是单模式串匹配,而AC自动机是多模式串自动匹配。不过这道题里面只碰到了AC自动机的
建立自动机和在Trie树插入这两个操作,如果想看AC自动机的模板的话,去做一下HDU2222。
关于Trie树,看百度百科就可以了,主要看那张图,别搭理底下的废话。
【Trie树的文章】:
http://baike.baidu.com/albums/2759664/2759664/0/0.html#0$9252ae7e96e893610dd7da67
http://baike.baidu.com/view/2759664.htm
我们要做的,就是在Trie树上【每个节点】增加一个fail指针,指向与自己有最长公共前缀
AC自动机恶心就恶心在这个Fail指针上,想法跟KMP匹配的失配函数next[i]差不多。
fail指针的性质是这样的:
1.我们设置某节点fail指针指向的节点为k(如果node[k]是根节点root的子节点,那么距离为0),node[k]与根root(root什么都不放,Trie树性质)的距离是m
2.那么,fail指针往上m个(就是沿着自己父节点向上回溯m次)节点的内容,与node[k]到根的节点内容完全一致
↑好好想一下这句话,如果想不通的话,看http://www.mobius-strip.com/archives/88.html 这里的图2
跟KMP的next函数很像不是么。。
fail指针的建立,是用一个BFS完成的,根据BFS的性质那么肯定是长串指向短串(←from lpp)
好的,那我们做这道题就很简单了
对于Trie树上每一个是单词末尾的节点,我们将其cnt++
在AC自动机的Build过程(其实就是寻找fail指针)的过程中。
我们对于某节点的儿子AC_nodes[tmp].next[i]的sum和(也就是题目要求的东西)可以这么求
在其父节点AC_nodes[tmp].sum和其fail指针指向的(int t = AC_nodes[AC_nodes[tmp].next[i]].fail),AC_nodes[t].sum节点
里面选一个大的,然后加上儿子的cnt就可以了。
结合AC自动机的性质想一下这个过程,其实不是特别难。
本题水于HDU2222,至于我为什么WA那么多次。。。
我只能说智商捉急害死人。
*/
struct node
{
int fail;
int sum;
int cnt;
int next[26];
void init()
{
fail = -1;
sum = 0;
cnt = 0;
memset(next,-1,sizeof(next));
}
};
int count = 1;
node AC_nodes[MAX_SIZE];
queue<int> q;
char buffer[1010] ={0};
void AC_insert(char* s)
{
int k = 0 ; //<- root;
int len =strlen(s);
for(int i = 0 ; i < len ; i++)
{
int p = s[i] - 'a';
if(AC_nodes[k].next[p] == -1)
{
AC_nodes[count].init();
AC_nodes[k].next[p] = count++;
}
k = AC_nodes[k].next[p];
}
AC_nodes[k].cnt++;
}
void AC_build()
{
q.push(0);
AC_nodes[0].fail = -1;
while(!q.empty())
{
int tmp = q.front();
q.pop();
for(int i = 0 ; i < 26; i ++)
{
if( AC_nodes[tmp].next[i] != -1 )
{
if( tmp == 0 )
{
AC_nodes[ AC_nodes[tmp].next[i] ].fail = tmp;
AC_nodes[AC_nodes[tmp].next[i]].sum = AC_nodes[tmp].sum + AC_nodes[AC_nodes[tmp].next[i]].cnt;
}
else
{
int p = AC_nodes[tmp].fail;
while(p != -1)
{
if( AC_nodes[p].next[i] != -1)
{
AC_nodes[AC_nodes[tmp].next[i]].fail = AC_nodes[p].next[i];
break;
}
p = AC_nodes[p].fail;
}
if( p == -1 )
{
AC_nodes[AC_nodes[tmp].next[i]].fail = 0;
}
AC_nodes[AC_nodes[tmp].next[i]].sum = max( AC_nodes[AC_nodes[AC_nodes[tmp].next[i]].fail].sum,AC_nodes[tmp].sum)+AC_nodes[AC_nodes[tmp].next[i]].cnt;
}
q.push(AC_nodes[tmp].next[i]);
}
}
}
while(!q.empty())
q.pop();
}
int AC_query()
{
int res = -1;
for(int i = 0 ; i < count ; i++)
{
res = max(AC_nodes[i].sum,res);
}
return res;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("B:\\acm\\SummerVacation\\String-I\\B.in","r",stdin);
freopen("B:\\acm\\SummerVacation\\String-I\\B.out","w",stdout);
#endif
int num;
while(scanf("%d\n",&num) != EOF && num != 0)
{
count = 1;
AC_nodes[0].init();
for(int i = 0 ; i < num; i++)
{
gets(buffer);
AC_insert(buffer);
}
AC_build();
//#define DBG
#ifdef DBG
for(int i = 0 ; i < count ; i++)
{
printf("Node %d\n",i);
printf("Fail is %d\n",AC_nodes[i].fail);
printf("Num is %d\n",AC_nodes[i].cnt);
printf("Sum is %d\n",AC_nodes[i].sum);
}
#endif
int ans = AC_query();
printf("%d\n",ans);
}
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
【0.9%】SPOJ7758 Grwoing Strings 解题报告 + AC代码 + 思路 + AC自动机简短总结
最新推荐文章于 2019-03-18 17:02:14 发布