POJ1625----AC自动机+大数+DP

题目地址:http://poj.org/problem?id=1625

题目意思:

给你一个字典,里面有n个不同的字符

然后给你p个不允许出现的字符串

要你求出在由字典里的字符组成的一个m长度的字符串不含禁止字符串的种数有多少

解题思路:

假设在一个AC自动机上,状态j的长度已经有i了,用dp[i][j]表示

那么dp[i+1][tmp] += dp[i][j]  其中tmp这个节点可以由j合法的转移(合法就是不会出现禁止的情况)

再就是本题的数据规模很大,50^50,所以要用大数

再就是要注意几个细节

比如最后大数的结果为0的话

或者是当上面说的j已经是非法点了,那么就不能跳转了

再就是本题输入的char可能会有到255,那么会出现负数,所以我用了hash的方法来重定向下标

下面上代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

const int maxnode = 120;
const int size=256;

int hash[size];
int wordn,m,p;

struct AC
{
    int ch[maxnode][size];
    int f[maxnode];
    bool val[maxnode];
    int sz;

    void init()
    {
        sz=1;
        memset(ch[0],-1,sizeof(ch[0]));
        val[0]=false;
    }

    void insert(char *s)
    {
        int len = strlen(s);
        int u=0;
        for(int i=0;i<len;i++)
        {
            int c=hash[s[i]+128];
            if(-1 == ch[u][c])
            {
                memset(ch[sz],-1,sizeof(ch[sz]));
                val[sz]=false;
                ch[u][c] = sz++;
            }
            u=ch[u][c];
        }
        val[u] = true;
    }

    void getfail()
    {
        queue<int> q;
        for(int i=0;i<wordn;i++)
        {
            int u = ch[0][i];
            if(-1!=u)
            {
                f[u]=0;
                q.push(u);
            }
            else
                ch[0][i]=0;
        }

        while(!q.empty())
        {
            int r=q.front();
            q.pop();
            //printf("q\n");
            val[r] |= val[f[r]];

            for(int i=0;i<wordn;i++)
            {
                int &v=ch[r][i];
                if(-1!=v)
                {
                    q.push(v);
                    f[v]=ch[f[r]][i];
                }
                else
                {
                    v=ch[f[r]][i];
                }
            }
        }
    }

}ac;

struct bignumber
{
    int a[100];
    void init()
    {
        memset(a,0,sizeof(a));
    }

    void print()
    {
        int j=99;
        while(a[j]==0)
            j--;
        if(j<0)
        {
            printf("0\n");
            return ;
        }
        while(j>=0)
        {
            printf("%d",a[j]);
            j--;
        }

        printf("\n");
    }

};

void add(bignumber &x,bignumber y)
{
    for(int i=0;i<99;i++)
    {
        x.a[i]+=y.a[i];
        x.a[i+1]+=x.a[i]/10;
        x.a[i]=x.a[i]%10;
    }
}

bignumber dp[55][120];

void dp_ac()
{
    memset(dp,0,sizeof(dp));
    dp[0][0].a[0]=1;
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<ac.sz;j++)
        {
            if(ac.val[j])
                continue;
            for(int k=0;k<wordn;k++)
            {
                int tmp = ac.ch[j][k];
                if(!ac.val[tmp] && !ac.val[j])
                {
                    add(dp[i+1][tmp],dp[i][j]);
                }
            }
        }

    }

    bignumber ans;
    ans.init();
    for(int i=0;i<ac.sz;i++)
    {
        add(ans,dp[m][i]);
    }
    ans.print();
}



int main()
{
    while(~scanf("%d%d%d",&wordn,&m,&p))
    {
        ac.init();
        char str[100];
        getchar();
        gets(str);
        memset(hash,0,sizeof(hash));
        for(int i=0;i<wordn;i++)
        {
            hash[str[i]+128]=i;
        }
        for(int i=0;i<p;i++)
        {
            gets(str);
            ac.insert(str);
        }
        ac.getfail();
        dp_ac();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值