Censored! POJ - 1625(AC自动机 + dp +高精度模板)

题目链接

题目大意:给你一个字母表,给定一些敏感字符串,问长度为m且不含任意敏感字符串的串有多少个。(字符全部来自字母表)

思路:首先第一个坑点是输入的字符是unsigned char,可能出现负的ASCII码值,我的解决方法是整体加128。 此外,这题和POJ2778有点类似,不过POJ2778不需要用大整数,用矩阵快速幂可以过,但这题需要用高精度存答案,高精度+矩阵快速幂会tle。

所以我们用动态规划,对建好的状态机进行状态转移(AC自动机建状态图的过程就不赘述了),令dp[i][j]为目前走了i步的情况下,到达j号节点的方案数。初始化dp[0][0]=1

状态转移方程:dp[i][j]=Σdp[i-1][k] (k为j的"父亲"节点)

转移的具体实现并不是枚举j的每个父亲k,这样不现实,而是枚举i-1状态下每个节点,再枚举每个孩子u,以此更新dp[i][u]

代码其实很好理解:

dp[0][0].set(1);//其余格子初始化成0
    for(int i=1;i<=m;i++)
    {
        for(int j=0;j<=tot;j++)//枚举i-1状态下的根节点0~最后一个节点tot
        {
            for(int k=0;k<n;k++)
            {
                int u=tree[j][k];//枚举j的每一个孩子u进行更新
                if(!isstr[u])
                {
                    dp[i][u]=dp[i][u]+dp[i-1][j];
                }
            }
        }
    }
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define INF 0x3f3f3f
#define LL unsigned long long
const int maxn=1e2+20;
struct BigInteger{
    int A[25];
    enum{MOD = 10000};
    BigInteger(){memset(A, 0, sizeof(A)); A[0]=1;}
    void set(int x){memset(A, 0, sizeof(A)); A[0]=1; A[1]=x;}
    void print(){
        printf("%d", A[A[0]]);
        for (int i=A[0]-1; i>0; i--){
            if (A[i]==0){printf("0000"); continue;}
            for (int k=10; k*A[i]<MOD; k*=10) printf("0");
            printf("%d", A[i]);
        }
        printf("\n");
    }
    int& operator [] (int p) {return A[p];}
    const int& operator [] (int p) const {return A[p];}
    BigInteger operator + (const BigInteger& B){
        BigInteger C;
        C[0]=max(A[0], B[0]);
        for (int i=1; i<=C[0]; i++)
            C[i]+=A[i]+B[i], C[i+1]+=C[i]/MOD, C[i]%=MOD;
        if (C[C[0]+1] > 0) C[0]++;
        return C;
    }
    BigInteger operator * (const BigInteger& B){
        BigInteger C;
        C[0]=A[0]+B[0];
        for (int i=1; i<=A[0]; i++)
            for (int j=1; j<=B[0]; j++){
                C[i+j-1]+=A[i]*B[j], C[i+j]+=C[i+j-1]/MOD, C[i+j-1]%=MOD;
            }
        if (C[C[0]] == 0) C[0]--;
        return C;
    }
};
char alpha[55];
BigInteger dp[maxn][maxn];//初始化是0
//思路:建好状态机之后,进行dp,dp[i][j]表示从根节点出发走了i步,走到j节点时的方案数,
//dp[i][j]=dp[i-1][k]  其中j是k的父亲节点,转移到i的时候,对每个节点j都更新一下所有的孩子
int book[600];
char s[55];
int n,m,p;
int tree[maxn][55];
int tot;
bool isstr[maxn];
int fail[maxn];
void Insert(char *str)
{
    int root=0,len=strlen(str);
    for(int i=0;i<len;i++)
    {
        int id=book[str[i]+128];
        if(!tree[root][id]) tree[root][id]=++tot;
        root=tree[root][id];
    }
    isstr[root]=true;
}
void getfail()
{
    queue<int> q;
    for(int i=0;i<n;i++)
    {
        if(tree[0][i]) q.push(tree[0][i]);
    }
    while(!q.empty())
    {
        int v=q.front();
        q.pop();
        if(isstr[fail[v]]) isstr[v]=true;
        for(int i=0;i<n;i++)
        {
            if(tree[v][i]) 
            {
                fail[tree[v][i]]=tree[fail[v]][i];
                q.push(tree[v][i]);
            }
            else tree[v][i]=tree[fail[v]][i];
        }
    }
}
int main()
{
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    scanf("%d%d%d",&n,&m,&p);
    scanf("%s",alpha);
    for(int i=0;i<n;i++)//每个节点有n个孩子位置
    {
        book[alpha[i]+128]=i;
    }
    while(p--)
    {
        scanf("%s",s);
        Insert(s);
    }
    getfail();
    dp[0][0].set(1);
    for(int i=1;i<=m;i++)
    {
        for(int j=0;j<=tot;j++)//枚举根节点0~最后一个节点tot
        {
            for(int k=0;k<n;k++)
            {
                int u=tree[j][k];//枚举j的每一个孩子u进行更新
                if(!isstr[u])
                {
                    dp[i][u]=dp[i][u]+dp[i-1][j];
                }
            }
        }
    }
    BigInteger sum;
    for(int i=0;i<=tot;i++) 
    {
        if(!isstr[i]) sum=sum+dp[m][i];
    }
    sum.print();
    system("pause");
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值