HDU 6096 (String) AC自动机

题目大意

现在有N个单词,Q次询问,每次询问包含两个字符串a和b,求出以a为前缀且以b为后缀的单词共有多少个,其中a和b不互相覆盖。

解题思路

题目中所说的a和b不互相覆盖的含义是当 a =“ac”b = “cm”时,“acm”串不满足条件,因为其以a为前缀且以b为后缀时,a和b相互覆盖了。

题解中所说的做法没太看懂,现在给出一种巧妙构造并利用AC自动机解题的方法。

由于询问的时候,每次给出两个字符串并不便于操作,不妨将后缀与前缀拼接在一起,并使用特殊的符号进行标记。将所有询问离线,建立AC自动机。并将给出的N个单词拼在一起,具体操作为:

1.将每个单词复制一份拼接在原单词后面,中间使用一种分隔符连接,此分隔符与询问中的区分前后缀的分隔符相同,构成新的单词。

2.将新的单词依次连接起来,中间使用另一种分隔符连接,将所有单词连接成一篇文章。

3.原问题转化为,求出一篇文章中的存在的每种模式串的数量,是经典的AC自动机求解问题。

为了方便理解,此处以样例作为栗子

Sample Input

**1
4 4
aba
cde
acdefa
cdef
a a
cd ef
ac a
ce f**

将给出的单词拼接成文章为:

aba|aba#cde|cde#acdefa|acdefa#cdef|cdef#

将前后缀处理为新的字符串:

S1=“a|a”,S2=”ef|cd”,S3=“a|ac”,S4 = “f|ce”

将S1-S4依次insert,建成AC自动机,离线询问

扫描文章,对于每次询问依次回答即可

小Tips:由于题目中给出了前后缀不能覆盖的条件,所以扫描文章的时候需要加入特判。

附上代码

#include 
#include 
#include 
#include 
#define  maxnode 2005000
#define sigma 30
using namespace std;
typedef long long ll;
int cnt=1;
char que[2550000],in[2000000],head[2000000],tail[2000000],key[2000000];
int ask[1001000],res[1010000];
struct ac_automation
{
    int ch[maxnode][sigma];
    int val[maxnode];
    int last[maxnode];
    int f[maxnode];
    int num[maxnode];
    int dep[maxnode];
    int sz,ans;
    void clear()
    {
        sz=1;ans=0;
        memset(ch[0],0,sizeof(ch[0]));
        memset(last,0,sizeof(last));
        memset(val,0,sizeof(val));
        memset(num,0,sizeof(num));
    }
    int idx(char sign)
    {
        if(sign>='a'&&sign<='z') return sign-'a';
        else if(sign=='|') return 26;
    }
    void insert(char s[],int k)
    {
        int u=0;
        int mx=strlen(s);
        for(int i=0;s[i];i++)
        {
            int c=idx(s[i]);
            if(!ch[u][c])
            {
                memset(ch[sz],0,sizeof(ch[sz]));
                ch[u][c]=sz++;
            }u=ch[u][c];
        }
        dep[u]=mx;
        if(!val[u])
            val[u]=cnt++;
        ask[k]=val[u];
    }
    void build()
    {
        f[0]=0;
        queue<int>q;
        for(int i=0;iif(ch[0][i])
            {
                f[ch[0][i]]=0;
                q.push(ch[0][i]);
                last[ch[0][i]]=0;
            }
        }
        while(!q.empty())
        {
            int now=q.front();
            q.pop();
            for(int i=0;iint son=ch[now][i];
                if(!son)
                {
                    ch[now][i]=ch[f[now]][i];
                    continue;
                }
                q.push(son);
                f[son]=ch[f[now]][i];
                last[son]=val[f[son]]?f[son]:last[f[son]];
            }
        }
    }
    void find(char *s)
    {
        int u=0,left=0,right=0,mid=0;
        for(int i=0;s[i];i++)
        {
            if(s[i]=='#') left=i;
            else if(s[i]=='|') mid=i;
            right=i;
            int c=idx(s[i]);
            u=ch[u][c];
            if(val[u]&&dep[u]<=mid-left)//特殊判断操作,当前后缀之和小于等于原单词长度才进行计数
                print(u);
            else
                print(last[u]);
        }
    }
    void print(int u)
    {
        if(u)
        {
            res[val[u]]++;
            print(last[u]);
        }
    }
}ac;
int main()
{
//    freopen("1001.in","r",stdin);
//    freopen("out.txt","w",stdout);
    int t,n,q;
    scanf("%d",&t);
    while(t--)
    {
        memset(res,0,sizeof(res));
        ac.clear();
        scanf("%d%d",&n,&q);
        int cont=1;
        que[0]='#';
        for(int i=0;iscanf("%s",in);
            int len=strlen(in);
            for(int j=0;j'|';
            cont=cont+len+1;
            for(int j=0;j'#';
            cont=cont+len+1;
        }
        que[cont]=0;
        for(int i=1;i<=q;i++)
        {
            scanf("%s",head);
            scanf("%s",tail);
            int len=strlen(tail);
            tail[len]='|';len++;
            int len2=strlen(head);
            for(int j=0;j0;
            ac.insert(tail,i);
        }
        ac.build();
        ac.find(que);
        for(int i=1;i<=q;i++)
            printf("%d\n",res[ask[i]]);
    }
    return 0;
}

你可能感兴趣的:(AC自动机)