HDU-2243 考研路茫茫——单词情结(AC自动机 + 矩阵快速幂)

题目描述

背单词,始终是复习英语的重要环节。在荒废了3年大学生涯后,Lele也终于要开始背单词了。
一天,Lele在某本单词书上看到了一个根据词根来背单词的方法。比如"ab",放在单词前一般表示"相反,变坏,离去"等。

于是Lele想,如果背了N个词根,那这些词根到底会不会在单词里出现呢。更确切的描述是:长度不超过L,只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个呢?这里就不考虑单词是否有实际意义。

比如一共有2个词根 aa 和 ab ,则可能存在104个长度不超过3的单词,分别为
(2个) aa,ab,
(26个)aaa,aab,aac…aaz,
(26个)aba,abb,abc…abz,
(25个)baa,caa,daa…zaa,
(25个)bab,cab,dab…zab。

这个只是很小的情况。而对于其他复杂点的情况,Lele实在是数不出来了,现在就请你帮帮他。

输入

本题目包含多组数据,请处理到文件结束。
每组数据占两行。
第一行有两个正整数N和L。(0<N<6,0<L<2^31)
第二行有N个词根,每个词根仅由小写字母组成,长度不超过5。两个词根中间用一个空格分隔开。

输出

对于每组数据,请在一行里输出一共可能的单词数目。
由于结果可能非常巨大,你只需要输出单词总数模2^64的值。

样例

2 3
aa ab
1 2
a
104
52





思路

摘自Chen_J_r的博客

又是一个AC自动机的好题。

本质上是bzoj1030poj2778的综合,只不过数据范围更加大,更加麻烦一些。

摘自poj2778的题解


首先,倘若这个问题中要组成的字符串的长度m比较小的话,题目的做法就类似bzoj1030,我们设dp[i][j]为当前长度为i的字符串处于Trie树中的第j号结点所具有的方案数,就可以通过转移方程dp[i][next[j][k]+=dp[i-1][j]求解。
但是在这个问题中,字符串的长度多达2e9,显然在这个长度下甚至连一个dp数组都开不下,显然用dp去做显然不太现实。因此我们考虑从图的角度审视这个问题。

我们分析之前所构造出的转移方程,dp[i+1][j]+=dp[i][next[j][k]],我们发现这个转移方程在Trie图上的表示为:结点j能够到达结点next[j][k],即两个结点在Trie图上是可达的。

而我们题目中所要求的长度为n的方案数,实质上等价于要求我们从Trie图的根节点开始转移,一直转移n次,且保证转移到的结点不是结尾字符所得到的方案数。

首先如果数据范围较小,我们直接可以用一个dp去求出不符合条件的方案数,用总方案数-不合法的方案数即为答案。

但是在这个题中数据范围极大,因此我们必须用类似poj2778中的构造Trie图跑矩阵快速幂(见上)的方法去做。

而因为题目中让我们求的是至少包含1个模式串的方案数,因此我们需要求出 k = 1 m A k \sum_{k=1}^{m}A^k (A为邻接矩阵)。因此我们只需要在基础的构建矩阵的过程中使矩阵多开一维,使得第i+1列全为1作为求和

另外,因为要求至少包含一个模式串的方案数,因此在这种情况下,总方案数为: 26 + 2 6 2 + 2 6 3 + . . . + 2 6 m 26+26^2+26^3+...+26^m
显然这个式子直接去快速幂去求必定也会超时,因此我们也得对这个式子进行矩阵优化,对于上述式子有: S = 26 + 2 6 2 + 2 6 3 + . . . + 2 6 n S=26+26^2+26^3+...+26^n ,设有 F ( n ) = S + 1 F(n)=S+1 ,因此 F ( n ) F(n) 可转化成矩阵形式: F ( n ) = 26 F ( n 1 ) + 1 F(n) = 26 * F(n-1) + 1 ,此后再使用以此矩阵快速幂求解并让求出的两个结果相减即可。

  • ps:因为题目中要求我们对2^64取模,因此我们只需要将所有变量开成unsigned long long即可(溢出时会自动对2^64取模)。

  • 注意build中的一个操作

end[now] |= end[fail[now]]; 

终点状态需要转移,比如有单词ase、sex,对于ase的e节点,它的x结点的end应当是1(e的fail明显是sex的e结点,而这个e结点的x结点end是1),因为asex单词包含了sex,也属于出现了’sex’的情况。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <queue>

using namespace std;
typedef unsigned long long LL;

const int maxn = 50+5;
int n, m;
char st[maxn];

struct Matrx{
    LL v[maxn][maxn], n;
    Matrx(){}
    Matrx(int _n){
        n = _n;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++)
                v[i][j] = 0;
        }
    }
};

Matrx mul(Matrx &a, Matrx &b){
    Matrx res;
    res = Matrx(a.n);
    for(int i=0;i<a.n;i++)
    {
        for(int j=0;j<a.n;j++)
        {
            for(int k=0;k<a.n;k++)
                res.v[i][j] += a.v[i][k] * b.v[k][j];
        }
    }
    return res;
}

Matrx powMod(Matrx a, LL n)
{
    Matrx res = Matrx(a.n);
    for(int i=0;i<a.n;i++)
        res.v[i][i] = 1;
    while(n){
        if(n&1) res = mul(res, a);
        a = mul(a, a);
        n>>=1;
    }
    return res;
}

struct Trie{
    int next[500010][26], fail[500010], end[500010];
    int root, L;
    int newnode()
    {
        for(int i=0;i<26;i++)
            next[L][i] = -1;
        end[L] = 0;
        return L++;
    }
    void Init()
    {
        L = 0;
        root = newnode();
    }
    void insert(char buf[]){
        int len = (int)strlen(buf);
        int now = root;
        for(int i=0;i<len;i++)
        {
            if(next[now][buf[i]-'a']==-1)
                next[now][buf[i]-'a'] = newnode();
            now = next[now][buf[i]-'a'];
        }
        end[now]++;
    }
    
    void build(){
        queue<int> Q;
        fail[root] = root;
        for(int i=0;i<26;i++)
            if(next[root][i]==-1)
                next[root][i] = root;
            else{
                fail[next[root][i]] = root;
                Q.push(next[root][i]);
            }
        while(!Q.empty()){
            int now = Q.front();
            Q.pop();
            for(int i=0;i<26;i++)
                if(next[now][i]==-1)
                    next[now][i] = next[fail[now]][i];
                else{
                    fail[next[now][i]] = next[fail[now]][i];
                    Q.push(next[now][i]);
                }
            /********这里需要注意**********/
            end[now] |= end[fail[now]]; 
            /*****************************/
        }
    }

    Matrx get_mat()
    {
        Matrx res = Matrx(L+1);
        for(int i=0;i<L;i++)
        {
            for(int j=0;j<26;j++){
                if(end[next[i][j]]) continue;
                res.v[i][next[i][j]]++;
            }
        }
        for(int i=0;i<L+1;i++){
            res.v[i][L] = 1;
        }
        return res;
    }
}ac;

int main()
{
    while(~scanf("%d%d", &n, &m))
    {
         ac.Init();
         for(int i=0;i<n;i++){
            cin >> st;
            ac.insert(st);
         }
         ac.build();
         Matrx m1 = ac.get_mat();
         LL res = 0;
         m1 = powMod(m1, m);
         for(int i=0;i<m1.n;i++){
            res += m1.v[0][i]; // 从 根节点 到其他节点的不经过结尾的路径数
         }
         res--; // ? 应该是减去(0,0)位置的1(即根节点到根节点自己,是没有意义的)
         Matrx m2 = Matrx(2);
         m2.v[0][0] = 26;
         m2.v[1][1] = m2.v[0][1] = 1;
         m2 = powMod(m2, m);
         LL ans = m2.v[0][0] + m2.v[0][1];
         ans--; // F(n) = S+1, S = 26 + 26^2 + 26^3 + …… + 26^n, F(n) = F(n-1) + 1
         cout << ans - res << endl;

    }
    return 0;
}
发布了40 篇原创文章 · 获赞 15 · 访问量 1811

猜你喜欢

转载自blog.csdn.net/irimsky/article/details/104585602