hdu 6138 Fleet of the Eternal Throne(AC自动机)

http://acm.hdu.edu.cn/showproblem.php?pid=6138
题目大意:给出n个字符串后,会有q个询问,每个询问会输入两个数,记为x,y。然后要求的是,x,y的 最长公共子串,而且这个最长公共子串要是n个字符串中某个字符串的前缀。输出这个串的长度。
解题思路:这题的题意很明确,首先我们要求出x,y串的公共子串,然后要在n个字符串中出现过。这样就可以用的AC自动机。AC自动机原来是用来多模式匹配的,原来每个节点维护的值有一个标记是否是一个完整的字符串,然而由于该题是要前缀长度,我们就要队节点维护的值有所改进,每个节点维护其所代表的前缀的长度即可。这样的话,我们可以用这n个字符串构建字典树,然后通过BFS找到每个节点对应的fail指针,接下来,由于我们要找的是第x个字符串和第y个字符串的最长公共字串长度,且该字串是n个字符串中某个字符串的前缀。因此我们需要用第x个字符串和第y个字符串在字典树上进行一个匹配。当第x个字符串去匹配的时候,我们要做的就是把所有在匹配时经过的节点都标记一下。代表这些节点到字典树的根所代的字符串既是某个字符串的前缀,还在第x个字符串中。同理我们让第y个字符串在按相同的方法跑一遍AC自动机,有所不同的是,第一个串跑完之后已经留下标记,我们可以在第2个串跑的时候,直接通过检测标记来确定某个节点所代表的字符串是不是x,y的共同子串。然后边检测边求解答案就可以了。

#include <bits/stdc++.h>

using namespace std;
struct Trie
{
    int len,flag;
    Trie *fail;
    Trie *child[26];
    Trie()
    {
        flag = 0;
        len = 0;
        fail = NULL;
        for(int i=0; i<26; i++)
            child[i] = NULL;
    }
};
Trie *root,*current,*temp;
string str[100005];
int ans;
void _insert(string s)
{
    current = root;
    for(int i=0; i<s.length(); i++)
    {
        int index = s[i] - 'a';
        if(current->child[index] == NULL)
        {
            temp = new Trie;
            temp->len = current->len+1;
            current->child[index] = temp;
        }
        current = current->child[index];
       // (current->len)++;
    }
}
void Find_fail()
{
    queue<Trie*> Q;
    Q.push(root);
    while(!Q.empty())
    {
        current = Q.front();
        Q.pop();
        for(int i=0; i<26; i++)
        {
            if(current->child[i]!=NULL)
            {
                if(current==root)
                    current->child[i]->fail = root;
                else
                {
                    Trie *p = current->fail;
                    while(p)
                    {
                        if(p->child[i])
                        {
                            current->child[i]->fail = p->child[i];
                            break;
                        }
                        p=p->fail;
                    }
                    if(p==NULL)
                        current->child[i]->fail = root;
                }
                Q.push(current->child[i]);
            }
        }
    }
}
void _search(string s,int f)
{
    current = root;
    for(int i=0; i<s.length(); i++)
    {
        int index = s[i]-'a';
        while(!current->child[index]&&current!=root)
            current = current->fail;
        current = current->child[index];
        if(!current)  current = root;
        temp = current;
        while(temp!=root)
        {
            temp->flag = f;
            temp = temp->fail;
        }
    }
}
void AC_authomation(string s,int f)
{
    current = root;
    for(int i=0; i<s.length(); i++)
    {
        int index = s[i]-'a';
        while(!current->child[index]&&current!=root)
            current = current->fail;
        current = current->child[index];
        if(!current)  current = root;
        temp = current;
        while(temp!=root)
        {
            if(temp->flag == f)
            {
                ans = max(temp->len,ans);
            }
            temp = temp->fail;
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,q,x,y;
        root = new Trie;
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
        {
            cin>>str[i];
            _insert(str[i]);
        }
        Find_fail();
        scanf("%d",&q);
        for(int i=1; i<=q; i++)
        {
            ans = 0;
            scanf("%d %d",&x,&y);
            _search(str[x],i);
            AC_authomation(str[y],i);
            printf("%d\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值