【0.9%】SPOJ7758 Grwoing Strings 解题报告 + AC代码 + 思路 + AC自动机简短总结

5 篇文章 0 订阅
3 篇文章 0 订阅
#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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值