【AC自动机+矩阵幂的和】考研路茫茫――单词情结 HDU - 2243 (求包含特殊字符串的长度不超过n的字符串总数)

题目链接

考研路茫茫――单词情结 HDU - 2243

题目

背单词,始终是复习英语的重要环节。在荒废了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实在是数不出来了,现在就请你帮帮他。
Input
本题目包含多组数据,请处理到文件结束。
每组数据占两行。
第一行有两个正整数N和L。(0

分析

总字符串个数为26+26^2+26^3+…+26^n.
包含词根的字符串个数为A+A^2+A^3+…A^n所得到的矩阵的第一行的值的和。A为根据AC自动机确定的状态转移图对应的矩阵。
这题就是POJ2778的变形。
DNA Sequence POJ - 2778
(求不包含特定字符串的长度为N的字符串总数)
变形的部分就是POJ3233
(给定矩阵A,求A+A^2+A^3+…+A^n的结果)
大致的解题思路就是
A+A^2+A^3+…+A^6=A+A^2+A^3+A^3(A+A^2+A^3).
递归求(A+A^2+A^3)并注意讨论下n的奇偶性即可。

AC代码

//187ms 13.1MB
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <queue>
typedef unsigned long long ll;
using namespace std;
const int maxn=50; //单词长度*单词数,结点总数
const int maxc=26; //树分支数

//在trie上添加后缀结点
//后缀结点:x的后缀结点为非x结点y,
//y的路径字符串为x的路径字符串的最长后缀且y的路径字符串是单词
struct ac_au
{
    int child[maxn][maxc];//trie
    int fail[maxn];//后缀结点
    int sta[maxn];//是否为含词根字符串的尾结点
    int cnt;//结点的个数
    int root; //根结点
    int newnode()
    {
        for(int i=0;i<maxc;i++)
            child[cnt][i]=-1;
        sta[cnt++]=0;
        return cnt-1;
    }
    void init()
    {
        cnt=0;
        root=newnode();
    }
    int getid(char ch)
    {
        return ch-'a';
    }
    void insert(char *s)//trie构建
    {
        int len=strlen(s),p=0;
        for(int i=0;i<len;i++)
        {
            int c=getid(s[i]);//第c个儿子
            int &num=child[p][c];
            if(num==-1)
                num=newnode();
            p=num;
        }
        sta[p]=1;//是词根字符串的尾结点
    }
    void build()//后缀结点的添加
    {
        int p=0;
        queue<int> que;
        for(int i=0;i<maxc;i++)
        {
            int &num=child[p][i];
            if(num==-1)
                num=p;//为下文铺垫,和树意义无关
            else
            {
                fail[num]=p;//有点多余,初始化都是0,理解算法过程有用
                que.push(num);
            }
        }
        while(!que.empty())
        {
            p=que.front();que.pop();
            int x=fail[p];//p的后缀结点为x
            //后缀结点的确定是通过bfs,由浅层到深层,故每一个在队列中的结点都确定了后缀结点
            if(sta[x]) sta[p]=1;//其后缀结点为含病毒字符串尾结点,
            //说明p也是含词根字符串尾结点,可能不是词根,但一定包含词根
            for(int i=0;i<maxc;i++)
            {
                int &num=child[p][i];//引用,改变num就改变child[p][i]
                if(num==-1)
                    num=child[fail[p]][i];
                else
                {
                    fail[num]=child[fail[p]][i];
                    que.push(num);
                }
            }
        }
    }

}ac;
struct matrix
{
    ll m[50][50];//相当于模2^64
    matrix()
    {
        for(int i=0;i<ac.cnt;i++)
            for(int j=0;j<ac.cnt;j++)
            m[i][j]=0;
    }
};
matrix mul(matrix A,matrix B)//矩阵乘法
{
    matrix C;
    for(int i=0; i<ac.cnt; i++)
        for(int j=0; j<ac.cnt; j++)
        {
            C.m[i][j]=0;
            for(int k=0; k<ac.cnt; k++)
            {
                ll tmp=A.m[i][k]*B.m[k][j];
                C.m[i][j]=(C.m[i][j]+tmp);
            }
        }
    return C;
}
matrix pow(matrix A,ll k)//矩阵快速幂
{
    matrix ans=A;
    matrix x=A;
    k--;
    while(k)
    {
        if(k&1)
            ans=mul(ans,x);
        k>>=1;
        x=mul(x,x);
    }
    return ans;
}
matrix getMatrix() //从AC自动机得到状态转移图对应的矩阵
{
    matrix A=matrix();
    for(int i=0; i<ac.cnt; i++)
        for(int j=0; j<maxc; j++)
        {
            //如果从u结点走到v结点不会使得字符串变成含词根字符串,则合法,可以连边
            int u=i;
            int v=ac.child[u][j];
            if(!ac.sta[v])
                A.m[u][v]++;
        }
    return A;
}
matrix add(matrix A,matrix B)//矩阵加法
{
    matrix ans;
    for(int i=0;i<ac.cnt;i++)
        for(int j=0;j<ac.cnt;j++)
        ans.m[i][j]=A.m[i][j]+B.m[i][j];
    return ans;
}
matrix sum(matrix A,int k)//求A+A^2+A^3+...+A^k
{
    matrix ans,x,y;
    if(k==1) return A;
    x=sum(A,k>>1);
    if(k&1)//另一种递归形式:将奇数看作偶数+A^k会超内存
    {
        y=pow(A,k/2+1);
        ans=add(x,y);
        ans=add(ans,mul(x,y));
    }
    else
    {
        y=pow(A,k>>1);
        ans=add(x,mul(x,y));
    }
    return ans;
}
ll _pow(int k)//快速幂
{
    ll ans=1,x=26;
    while(k)
    {
        if(k&1) ans=ans*x;
        k>>=1;
        x=x*x;
    }
    return ans;
}

ll all(ll k) //求26+26^2+26^3+...+26^k
{
    if(k==1)
        return 26;
    ll ans,x,y;
    x=all(k>>1);
    if(k&1)
    {
        y=_pow(k/2+1);
        ans=x+y+x*y;
    }
    else
    {
        y=_pow(k/2);
        ans=x+x*y;
    }
    return ans;
}
char s[105];
int main()
{

    int n,k;
    while(~scanf("%d%d",&n,&k))
    {
        ac.init();
        for(int i=1; i<=n; i++)
        {
            scanf("%s",s);
            ac.insert(s);
        }
        ac.build();
        matrix A=getMatrix();
        matrix ans=sum(A,k);
        ll res=0;
        ll tmp=all(k);
        for(int i=0;i<ac.cnt;i++)
            res=(res+ans.m[0][i]);
        res=tmp-res;
        printf("%I64u\n",res);//unsigned long long的输出格式为%I64u
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37685156/article/details/80486853