题解-文本生成器

[JSOI2007]文本生成器

古老的博文。

这题就是那种著名的AC自动机+dp题,算法唯一,如果你不会AC自动机,出门左转[传送门]

这题当中的那些单词就相当于AC自动机模板中的模式串,只不过这题当中是要求满足“包含至少一个模式串的”文本串而已。

所以先把模式串放到 \(\texttt{trie}\) 树上,然后用AC自动机的 \(\texttt{build()}\) 函数把 \(fail\) 指针构造出来,并把 \(ch[][]\) 数组进化为一条永通路,以便避免文本串匹配时一个模式串到结尾了没有地方可走的尴尬。

以上都是对模板的解说。 对于这题,我们发现“包含至少一个”这样的约束条件,就想到 答案\(=\)整体\(-\)剩余,先求出一个模式串都不包含的文本串数,然后求出没有约束条件下文本串有 \(26^m\) 种,就可以得出答案。

因为要求一个模式串都不包含的文本串数,所以 \(\texttt{insert(s)}\) 的时候在有字符串结尾的节点 \(x\) 上注 \(mk[x]=1\),如下:

void insert(char*s){
    int n=strlen(s+1),p=1;
    for(int i=1;i<=n;i++){
        int c=s[i]-'A'+1;
        if(!ch[p][c]) ch[p][c]=++cnt;
        p=ch[p][c];
    }
    mk[p]=1;
}

又因为如果一个字符串的后缀是模式串,那么它也不能出现在文本串中,所以在构造AC自动机 \(fail\) 指针的时候要令 \(mk[x]|=mk[fail[x]]\),如下:

void build(){
    for(int i=1;i<=26;i++) ch[0][i]=1;
    queue<int> q;while(q.size()) q.pop();q.push(1);
    while(q.size()){
        int x=q.front();q.pop();
        for(int c=1;c<=26;c++)
            if(ch[x][c]) fa[ch[x][c]]=ch[fa[x]][c],mk[ch[x][c]]
                |=mk[fa[ch[x][c]]],q.push(ch[x][c]);
            else ch[x][c]=ch[fa[x]][c];
    }
}

因为这题的文本串没有实体,不能通过AC自动机上 \(fail\) 指针一跳完事,所以根据AC自动机dp。\(dp[i][j]\) 表示长度为 \(i\) 且后缀字符为AC自动机上节点 \(j\) 的文本串数量,又文本串中不能出现模式串,所以有转移方程:

for(int i=1;i<=m;i++)
    for(int j=1;j<=t.cnt;j++)//全点参与
        for(int c=1;c<=26;c++) //沿着子节点生成文本串下一个字母
            if(!t.mk[t.ch[j][c]])  //避开模式串
                dp[i][t.ch[j][c]]=(dp[i][t.ch[j][c]]+dp[i-1][j])%mod;

如果AC自动机节点数为 \(cnt\),那么一个模式串都不包含的文本串数就为 \(\sum\limits_{i=1}^{cnt}dp[m][i]\),所以答案就是:

\[(26^m-\sum\limits_{i=1}^{cnt}dp[m][i])\mod 10007\]

因为模数小,只有 \(10007\),所以不必开 \(\texttt{long long}\),但如果你忘记取模了,\(\texttt{long long}\) 也救不了你。如果你懂了,那么蒟蒻就放代码了:

#include <bits/stdc++.h>
using namespace std;
const int N=70;
const int M=110;
const int mod=1e4+7;
class Trie{
public:
    int ch[N*M][30],cnt;
    bool mk[N*M];
    Trie(){cnt=1;}
    void insert(char*s){
        int n=strlen(s+1),p=1;
        for(int i=1;i<=n;i++){
            int c=s[i]-'A'+1;
            if(!ch[p][c]) ch[p][c]=++cnt;
            p=ch[p][c];
        }
        mk[p]=1;
    }
};
class Acam:public Trie{
public:
    int fa[N*M];
    void build(){
        for(int i=1;i<=26;i++) ch[0][i]=1;
        queue<int> q;while(q.size()) q.pop();q.push(1);
        while(q.size()){
            int x=q.front();q.pop();
            for(int c=1;c<=26;c++)
                if(ch[x][c]) fa[ch[x][c]]=ch[fa[x]][c],mk[ch[x][c]]
                    |=mk[fa[ch[x][c]]],q.push(ch[x][c]);
                else ch[x][c]=ch[fa[x]][c];
        }
    }
}t;
int n,m,dp[M][N*M],ans=1;
char s[M];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",s+1),t.insert(s);
    t.build();
    dp[0][1]=1;
    for(int i=1;i<=m;i++) for(int j=1;j<=t.cnt;j++)
        for(int c=1;c<=26;c++) if(!t.mk[t.ch[j][c]]) 
            dp[i][t.ch[j][c]]=(dp[i][t.ch[j][c]]+dp[i-1][j])%mod;
    for(int i=m,j=26;i;j=(j*j)%mod,i>>=1)
        if(i&1) ans=(ans*j)%mod;
    for(int i=1;i<=t.cnt;i++) ans=(ans-dp[m][i]+mod)%mod;
    printf("%d\n",ans);
    return 0;
}

后续学习推荐题目:

[NOI2011]阿狸的打字机
[SDOI2014]数数
[JSOI2009]密码

祝大家学习愉快!

猜你喜欢

转载自www.cnblogs.com/Wendigo/p/12444585.html