2017 Multi-University Training Contest - Team 8:Fleet of the Eternal Throne(AC自动机)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6138

题目意思:题目给出n个字符串后,会有m个询问,每个询问会输入两个数,记为x,y吧。

然后要求的是,x,y的最长公共字串长度,而且这个最长公共子串要是n个字符串中某个

字符串的前缀。

解题知识点是AC自动机。AC自动机原来是用来多模式匹配的,原来每个节点维护的值

有一个标记是否是一个完整的字符串,然而由于该题是要前缀长度,我们就要队节点维护

的值有所改进,每个节点维护其所代表的前缀的长度即可。

这样的话,我们可以用这n个字符串构建字典树,然后通过BFS找到每个节点对应的fail

指针,接下来,由于我们要找的是第x个字符串和第y个字符串的最长公共字串长度,且该字串

是n个字符串中某个字符串的前缀。因此我们需要用第x个字符串和第y个字符串在字典树上进行

一个匹配。当第x个字符串去匹配的时候,我们要做的就是把所有在匹配时经过的节点都标记一下。

代表这些节点到字典树的根所代表的字符串既是某个字符串的前缀,还在第x个字符串中。同理

我们让第y个字符串在按相同的方法跑一遍AC自动机,有所不同的是,第一个串跑完之后已经留下

标记,我们可以在第2个串跑的时候,直接通过检测标记来确定某个节点所代表的字符串是不是x,y

的共同子串。然后边检测边求解答案就可以了。


代码我是用数组实现AC自动机的,链表的害怕超时,并没有写。写数组的真费劲,感觉还是链表比较爽。


AC代码如下:

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <queue>

using namespace std;

const int allSon = 26;
const int maxn = 100010;
int node[maxn][allSon];   ///字典树节点
int fail[maxn];           ///fail指针
int len[maxn];            ///存放根到当前节点的字符串长度
int sign[maxn][2];        ///存放标记,sign[maxn][0]存放第一个串跑AC自动机的标记,sign[maxn][1]放第二个。
char patten[maxn];        ///存放文本串
int LCS;                  ///存放最长公共字串长度
int id;                   ///用来给节点编号
int len1[maxn];           ///存放第i个字符串在patten中的开始位置
int len2[maxn];           ///存在第i个字符串的长度
///将模式串插入到字典树
void insertPatten(int left,int right)  ///左右边界
{
    int p = 0;             ///0就是根节点
    int index = left;
    while(index < right)
    {
        int lowercase = patten[index]-'a';
        if(node[p][lowercase] == -1)   ///对应的节点不存在,创建节点
        {
            memset(node[id],-1,sizeof(node[id])); ///儿子节点置空
            len[id] = len[p]+1;                   ///该节点代表的前缀长度是其父节点加1
            fail[id] = -1;                        ///失败指针不存在
            node[p][lowercase] = id++;            ///相应位置存放节点编号
        }
        p = node[p][lowercase];
        index++;
    }
}
void build_AC_automaton()
{
    queue<int>qu;
    int p = 0;      ///指向根节点
    qu.push(p);
    while(!qu.empty())
    {
        p = qu.front();
        qu.pop();
        for(int i = 0; i < allSon; i++)
        {
            if(node[p][i] !=-1)  ///这个孩子存在
            {
                if(p == 0)      ///p是根节点,其fail指针指向根
                {
                    fail[node[p][i]] = 0;
                }
                else
                {
                    int temp = fail[p];
                    while(temp != -1)
                    {
                        if(node[temp][i] != -1)
                        {
                            fail[node[p][i]] = node[temp][i];
                            break;
                        }
                        temp = fail[temp];
                    }
                    if(temp == -1)
                        fail[node[p][i]] = 0;
                }
                qu.push(node[p][i]);
            }
        }
    }
}
///在AC自动机中进行匹配
void find_in_AC_automaton(int left,int right,int flag)
{
    int p = 0;   ///根节点
    int index = left;
    LCS = -1;
    while(index < right)
    {
        int lowercase = patten[index]-'a';
        while(node[p][lowercase]==-1 && p!=0)
            p = fail[p];
        p = node[p][lowercase];
        if(p == -1) p = 0;
        int temp = p;
        while(temp!=0 && sign[temp][flag] != flag)
        {
            sign[temp][flag] = flag;
            if(flag == 1)   ///flag=1代表第一个串已经跑过AC自动机了,现在是第2个串
            {
                ///代表这个前缀是两个串的公共子串,直接在第2个串跑AC自动机的时候计算答案
                if(sign[temp][0]!=-1 && sign[temp][1]!=-1)  
                {
                    LCS = max(LCS,len[temp]);
                }
            }
            temp = fail[temp];
        }
        index++;
    }
}
int main()
{
    int t,n,m,x,y;    ///测试数据组数
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        memset(node[0],-1,sizeof(node[0]));
        len[0] = 0;
        fail[0] = -1;
        id = 1;
        memset(len1,0,sizeof(len1));   ///len1[i]保存第i个串的开始位置
        memset(len2,0,sizeof(len2));   ///len2[i]保存第i个串的长度。
        ///将所有字符串存到同一个数组中,节省空间,并通过这两个数组结合实现快速访问
        int prelen = 0;
        for(int i = 1; i <= n; i++)
        {
            scanf("%s",patten+prelen);
            len2[i] = strlen(patten+prelen);   ///第i串的串长
            len1[i] = prelen;
            insertPatten(prelen,prelen+len2[i]);
            prelen += len2[i];
        }
        build_AC_automaton();
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d%d",&x,&y);
            memset(sign,-1,sizeof(sign));
            ///直接锁定所要查询的串在patten中的范围。
            find_in_AC_automaton(len1[x],len1[x]+len2[x],0);   ///跑两次AC自动机
            find_in_AC_automaton(len1[y],len1[y]+len2[y],1);  
            printf("%d\n",LCS);
        }
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值