HDU 2243 考研路茫茫——单词情结

本文介绍了一种结合AC自动机与矩阵快速幂的算法,用于解决字符串中至少包含一个特定子串的问题。通过构建AC自动机并利用矩阵运算,高效地计算出所有可能的字符串组合中排除特定模式后的数量。

此题其实为综合算法,AC自动机DP+矩阵二分快速求幂。

由于要求的是“至少”包含一个的情况,每次碰到这种词就必然要联想到反面,也就是一个都不包含的情况。

因为是对2的64次方取模,其实直接用unsigned long long类型即可,其结果就是直接对2^64取模,而且为正,输出用%I64u就行了

于是转成了求解长度为1到N的所有可能串减去长度为1到N不含词根的串,其结果就是此题的答案。

首先求解1到N长度的可能串就是26^1+26^2+.........+26^N用矩阵快速求幂和二分求幂和即可。

然后就是关键的不含词根的串怎么解决。


首先用字典树对输入的词根进行建树,没有的儿子标为-1。词根结尾标记为end=1。


然后用AC自动机对所有节点找出其“可行的”状态转移,然后建立虚子节点,虚子节点指向字典树根(空节点,0号节点)。

建立虚子节点的目的是让不存在的节点返回树头重新匹配。

其意义就是:这个节点所代表的字符的下一个字符可以是他的不为结尾(end=1)的子节点所代表的字符。

最重要的是用fail指针找出具有相同前缀的词根,如果具有相同前缀且fail指针指的节点为词根结尾(end=1)那么该节点所在的串必然有相同前缀,所以这个节点的end也为1。

AC自动机部分就完毕了。


然后就是利用AC自动机建立矩阵。

矩阵的坐标是节点的标号,意思就是这个节点能不能转到另一个节点。

所以只要该节点的儿子存在(不等于-1)且该儿子不为结尾(end!=1)就能进行转移(改点值++)。

矩阵建完就是矩阵的二分求幂模板和求和模板了。

附渣代码:

#include <iostream>
#include <queue>
#include <cstdio>
#define MAX 26
#define CH 26
using namespace std;
char s[MAX];
struct Mat{
    unsigned long long mat[MAX][MAX];
    void e(){
        int i,j;
        for(i=0;i<MAX;i++)
        for(j=0;j<MAX;j++)
        mat[i][j]=(i==j);
    }
    void clr(){
        int i,j;
        for(i=0;i<MAX;i++)
        for(j=0;j<MAX;j++)
        mat[i][j]=0;
    }
    void print(int n,char *s){
        int i,j;
        for(i=0;i<n;i++){
            for(j=0;j<n;j++){
                printf("%6I64u ",mat[i][j]);
            }
            printf("\n");
        }
        printf("%s\n",s);
    }
    Mat operator+(Mat b){
        Mat c;
        c.clr();
        int i,j;
        for(i=0;i<MAX;i++){
            for(j=0;j<MAX;j++){
                c.mat[i][j]=mat[i][j]+b.mat[i][j];
            }
        }
        return c;
    }
    Mat operator*(Mat b){
        Mat c;
        int i,j,k;
        c.clr();
        for(i=0;i<MAX;i++){
            for(k=0;k<MAX;k++){
                if(mat[i][k]==0)continue;
                for(j=0;j<MAX;j++){
                    if(b.mat[k][j]==0)continue;
                    c.mat[i][j]+=mat[i][k]*b.mat[k][j];
                }
            }
        }
        return c;
    }
    Mat operator^(int p){         //二分求幂
        Mat ret,a;
        int i,j;
        ret.e();
        for(i=0;i<MAX;i++)
        for(j=0;j<MAX;j++)
        a.mat[i][j]=mat[i][j];
        for(;p;p>>=1){
            if(p&1)ret=ret*a;
            a=a*a;
        }
        return ret;
    }
    Mat operator-(Mat b){
        Mat c;
        c.clr();
        int i,j;
        for(i=0;i<MAX;i++){
            for(j=0;j<MAX;j++){
                c.mat[i][j]=mat[i][j]-b.mat[i][j];
            }
        }
        return c;
    }
}A,B,C,e;                                             //矩阵模板结构体,重载了矩阵运算符。
int siz;
struct _AC{
    int chd[CH];
    int fail;
    bool end;
    void init(){
        for(int i=0;i<CH;i++)
            chd[i]=-1;
        end=0;
        fail=0;
    }
}node[MAX];                                          //AC自动机/字典树节点
void insert_trie(char *s)   //字典树插入函数
{
    int i,p;
    p=0;
    for(i=0;s[i]!='\0';i++){
        char c=s[i]-'a';
        if(node[p].chd[c]==-1){
            node[p].chd[c]=++siz;
            node[siz].init();
        }
        p=node[p].chd[c];
    }
    node[p].end=1;       //结尾标为1.
}
void ac_automaton()
{
    queue < int > Q;
    int i,father,son;
    Q.push(0);
    node[0].end=0;
    while(!Q.empty()){
        father=Q.front();
        Q.pop();
        node[father].end=node[father].end||node[node[father].fail].end;   //该节点的end值等于他的end值或者其fail指针指向的节点的end值。关键点!!!
        for(i=0;i<CH;i++){
            if(node[father].chd[i]!=-1){
                son=node[father].chd[i];
                if(father==0)node[father].fail=0;
                else {
                    node[son].fail=node[node[father].fail].chd[i];     //儿子的fail指针指向父亲的fail指针的这个儿子
                }
                Q.push(son);
            }
            else{
                if(father==0)node[father].chd[i]=0;
                else
                node[father].chd[i]=node[node[father].fail].chd[i];//建立虚子节点。
            }
        }
    }
}
void matrix()
{
    int i,j;
    A.clr();
    for(i=0;i<=siz;i++){
        for(j=0;j<CH;j++){
            if(node[i].chd[j]!=-1&&node[node[i].chd[j]].end==0){
                A.mat[node[i].chd[j]][i]++;        //可以转移就加1代表可以再多出一种转移路径
            }
        }
    }
    B.clr();
    B.mat[0][0]=1;
}
Mat plmul(Mat c,int k)//二分求幂和。
{
    char i,n=0;
    bool s[30];
    Mat a,b;
    while(k>0){
        s[n++]=(k&1);
        k>>=1;
    }
    b=a=c;
    for(i=n-2;i>=0;i--){
        a=a*(b+e);
        b=b*b;
        if(s[i]){
            b=b*c;
            a=a+b;
        }
    }
    return a;
}
int main()
{
    int i,n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        A.clr();B.clr();C.clr();e.e();
        node[0].init();
        siz=0;
        for(i=0;i<n;i++){
            scanf("%s",s);
            insert_trie(s);
        }
        ac_automaton();
        matrix();
        A=plmul(A,m);
        A=A*B;
        B.mat[0][0]=26;B.mat[1][0]=1;
        C.mat[0][0]=1;
        B=plmul(B,m);
        B=B*C;
        unsigned long long ans=B.mat[0][0];
        for(i=0;i<=siz;i++)
        ans-=A.mat[i][0];      //把所有节点的情况全部减掉就是答案了
        printf("%I64u\n",ans);
    }
    return 0;
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值