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

题目链接:点击打开链接

题目描述:给定一些词根,求至少含有一个词根的长度<=L的字符串有多少种?

解题思路:AC自动机+矩阵快速幂

分析:这道题目和poj2778 DNA Sequence 是一样的,如果还没做过那道题目建议先去做一下再做这道

1、首先要知道图的邻接矩阵幂的含义是什么?不知道的请看:点击打开链接

有了上述概念之后:

AC自动机本身就是一张图,AC自动机上的每个状态表示图中的一个顶点,每条经过a、b、c···的状态转移相当于图中的一条边,所以求长度为n的字符串就相当于求在图中从出发点经过n步所能到达任意顶点的方案总数,而本题所要求的是<=L的,我们只需要求邻接矩阵A的A^1+A^2+···+A^n的方案数即可

关于如何求包含词根的字符串,根据poj2778我们会求解不包含词根的字符串,只要用总的减去不包含的就可以了即:(26^1+26^2+···+26^n)-(A^1+A^2+···+A^n)

对于如何求解一个数的前n次幂的和与一个矩阵的前n次幂的和,我们可以使用矩阵快速幂

譬如:26^1+26^2+···+26^n

|1 26|    |0|

|0 26|    |1|

A^1+A^2+···+A^n

|E A| |0|

|0 A| |E|


代码如下:

#include <cstdio>
#include <cstring>
#include <queue>
typedef unsigned long long ll;
using namespace std;
struct Matrix{
    ll m[60][60];
    int L;
    Matrix(int len){
        L=len;
        for(int i=0;i<L;++i)
            for(int j=0;j<L;++j)
                m[i][j]=0;
    }
    Matrix operator*(const Matrix& b){
        Matrix t(L);
        for(int i=0;i<L;++i)
            for(int j=0;j<L;++j)
                for(int k=0;k<L;++k)
                    t.m[i][j]+=(m[i][k]*b.m[k][j]);
        return t;
    }
};
Matrix doexpmat(Matrix a,int num){
    if(num==1) return a;
    Matrix x(a.L*2);
    for(int i=0;i<a.L;++i) for(int j=0;j<a.L;++j) if(i==j)x.m[i][j]=1;
    for(int i=a.L;i<x.L;++i) for(int j=0;j<a.L;++j) x.m[i][j]=0;
    for(int i=0;i<a.L;++i) for(int j=a.L;j<x.L;++j) x.m[i][j]=a.m[i][j-a.L];
    for(int i=a.L;i<x.L;++i) for(int j=a.L;j<x.L;++j) x.m[i][j]=a.m[i-a.L][j-a.L];
    Matrix t(a.L*2);
    for(int i=0;i<t.L;++i) t.m[i][i]=1;
    num--;
    while(num){
        if(num&1) t=t*x;
        num=num>>1;
        x=x*x;
    }
    Matrix tt(a.L*2);
    for(int i=0;i<a.L;++i) for(int j=0;j<a.L;++j) tt.m[i][j]=a.m[i][j];
    for(int i=a.L;i<t.L;++i) for( int j=0;j<a.L;++j) tt.m[i][j]=a.m[i-a.L][j];
    t=t*tt;
    return t;
}
ll pows(ll a,int num){
    if(num==1) return a;
    Matrix x(2);
    x.m[0][0]=1; x.m[0][1]=a;
    x.m[1][0]=0; x.m[1][1]=a;
    Matrix t(2);
    t.m[0][0]=1; t.m[1][1]=1;
    num--;
    while(num){
        if(num&1) t=t*x;
        num=num>>1;
        x=x*x;
    }
    return (t.m[0][0]+t.m[0][1])*a;
}
struct Trie{
    int next1[30][26],fail[30];
    bool end1[30];
    int root,L;
    int newnode(){
        for(int i=0;i<26;++i) next1[L][i]=-1;
        end1[L++]=false;
        return L-1;
    }
    void init(){
        L=0;
        root=newnode();
    }
    void insertnode(char* str){
        int len=strlen(str),now=root;
        for(int i=0;i<len;++i){
            if(next1[now][str[i]-'a']==-1)
                next1[now][str[i]-'a']=newnode();
            now=next1[now][str[i]-'a'];
        }
        end1[now]=true;
    }
    void build(){
        fail[root]=root;
        queue<int> q;
        for(int i=0;i<26;++i){
            if(next1[root][i]==-1)
                next1[root][i]=root;
            else{
                fail[next1[root][i]]=root;
                q.push(next1[root][i]);
            }
        }
        while(!q.empty()){
            int now=q.front();q.pop();
            if(end1[fail[now]]) end1[now]=true;
            for(int i=0;i<26;++i){
                if(next1[now][i]==-1)
                    next1[now][i]=next1[fail[now]][i];
                else{
                    fail[next1[now][i]]=next1[fail[now]][i];
                    q.push(next1[now][i]);
                }
            }
        }
    }
    Matrix getMatrix(){
        Matrix t(L);
        for(int i=0;i<L;++i) for(int j=0;j<26;++j)
            if(!end1[next1[i][j]]) t.m[i][next1[i][j]]++;
        return t;
    }
};
int n,l;
char st[10];
Trie ac;
int main(){
    while(scanf("%d%d",&n,&l)!=EOF){
        ac.init();
        for(int i=0;i<n;++i){ scanf("%s",st); ac.insertnode(st); };
        ac.build();
        Matrix t = ac.getMatrix();
        t=doexpmat(t,l);
        ll ans=0;
        for(int i=0;i<ac.L;++i)
            ans+=t.m[0][i];
        ll tmp=pows(26,l);
        printf("%I64u\n",tmp-ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值