[poj1625] 检查Censored!


题目描述

Freeland的字母表中刚好包含了N个字母。Freeland语言(也被称为Freish)中的每一个句子中刚好包含了M个不间断的字母。所以在Freish语言中总共包括了N^M 种不同的句子。

不过最近在Grass Jr.先生当选Freeland总统之后,一些令他不愉快的字句被命令不能刊印,而任何句子之中如果包含有其中的一个都是严禁的。句子S中含有一个单词W, 如果W是一个S的一个子链,例如 存在当k >= 1,k+len(W)-1 <= M的时候 ,有S[k] = W[1], S[k+1] = W[2], … , S[k+len(W)-1] = W[len(W)], len(W)表示W的长度。每个用了一个严禁的词语的人都要被关进监牢10年。

找出一共有多少个能被Freeland的人们使用的不相同的句子,让他们避免因使用严禁的字句而被关进监牢中。


输入格式

第一行包括了三个integer型数字:
N——是Freish字母表中字母的数目。
M——Freish所有句子的总长度。
P——被禁止使用的字句。(1 <= N <= 50, 1 <= M <= 50, 0 <= P <= 10).

第二行包括了刚好N个不同的字母——Freish语言字母表中的字母(每一个的ASCII码都大于32)。
接下来的P行包括了所有被禁止使用的字句,每一个都不长于Q个字母(Q为M和10之间的较小者),而被禁止的字句中只含有Freish字母表中的字母。


输出格式

只输出一个integer型的数——Freeland的人们能够安全使用的不相同的句子的总数。


样例数据

样例输入

2 3 1
ab
bb

样例输出

5


题目分析

较麻烦的题,但是不要被代码长度吓到了
先讲一讲思路,给出了可用字母和不可用单词,
那么首先想到将不可用单词建立AC自动机。
那么什么情况下会用到禁止使用的单词呢?
我们将Trie树中间被标记的点叫做危险结点。
在AC自动机中不断遍历,如果遇到危险结点,就是不合法的
考虑如何遍历,这里需要用到AC自动机建立失败指针的一个技巧
若当前结点无儿子结点,把失败指针处的儿子拿过来。
如果当前结点有儿子结点,把失败指针处的危险标记拿过来,详细见代码。
这样就方便了后面找后继结点。

void buildfail() { //Bfs构造Fail指针
    queue<int>Q;
    Q.push(root); 
    while(!Q.empty()) {
        int Now=Q.front();
        Q.pop();
        for(int i=0; i<n; i++) {
            int Next=tree[Now].child[i];
            if(Next==0) { //儿子不存在
                if(tree[tree[Now].fail].child[i])tree[Now].child[i]=tree[tree[Now].fail].child[i];
                else tree[Now].child[i]=root;
                continue;
            }
            Q.push(Next);
            int fatherfail=tree[Now].fail; //父亲的失败指针
            while(fatherfail&&!tree[fatherfail].child[i])fatherfail=tree[fatherfail].fail; //寻找可退回点
            if(fatherfail)tree[Next].fail=tree[fatherfail].child[i]; //如果存在满足条件的点则设置失败指针
            else tree[Next].fail=root; //否则指回root
            tree[Next].flag|=tree[tree[Next].fail].flag;
        }
    }
}

有了这些基础,我们就可以动规了,f[i,j]表示从root->j的链上,长度为i的句子中的可用数目。
ans= cnti=1f[m,i]
初始化f[0,1]=1
有读者就要问了,如果可用字符不在AC自动机里怎么统计呢?没有关系,如果没有的也会因为失败指针累积到root去。
此题似乎完美的解决了,其实并没有,因为还要高精度

如果一直AC不了请看过来
原因:AC自动机是WA自动机
真正原因:
看看这组数据(来自poj的discuss)

32 50 10
、¥ウЖ┆q忏溴骁栝觌祉铒?
Β
驿?
馥?
癌ē?
く.?
く.铽
Β‘飒
ウq憝?
驿Γ飒
隘ルΒ°?

不保证我的输出一定正确,但你RE就一定错了,233Hash加上个100吧

23333,做完此题整个人都升华了


源代码

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
    int num=0,bj=1;
    char x=getchar();
    while(x<'0'||x>'9') {
        if(x=='-')bj=-1;
        x=getchar();
    }
    while(x>='0'&&x<='9') {
        num=num*10+x-'0';
        x=getchar();
    }
    return num*bj;
}
struct BigInteger {
    static const int BASE=10000; //高进制
    static const int WIDTH=4; //高进制位数
    vector<int>s;
    BigInteger() {}
    BigInteger(long long num) { // 构造函数
        *this=num;
    }
    //赋值
    BigInteger operator = (long long num) {
        s.clear();
        do {
            s.push_back(num%BASE);
            num/=BASE;
        } while(num>0);
        return *this;
    }
    //+
    BigInteger operator + (BigInteger& b) {
        BigInteger c;
        c.s.resize(max(s.size(),b.s.size())+1);
        for(int i=0; i<c.s.size()-1; i++) {
            int tmp1,tmp2;
            if(i>=s.size())tmp1=0;
            else tmp1=s[i];
            if(i>=b.s.size())tmp2=0;
            else tmp2=b.s[i];
            c.s[i]=tmp1+tmp2;
        }
        for(int i=0; i<c.s.size()-1; i++) {
            c.s[i+1]+=c.s[i]/BASE;
            c.s[i]%=BASE;
        }
        while(c.s.back()==0&&c.s.size()>1)c.s.pop_back();
        return c;
    }
    void operator += (BigInteger& b) {
        *this=*this+b;
    }
};
ostream& operator << (ostream& output,const BigInteger& x) {
    output<<x.s.back();
    for(int i=x.s.size()-2; i>=0; i--) {
        char buf[20];
        sprintf(buf,"%04d",x.s[i]);
        for(int j=0; j<strlen(buf); j++)output<<buf[j];
    }
    return output;
}
const int maxn=5000;
struct Tree {
    int child[305],fail,flag; //fail失败指针
    void clear() {
        memset(child,0,sizeof(child));
        fail=0;
        flag=0;
    }
};
int root=1,n,m,p,Index[5005];
struct Aho_Corasick_Automaton { //AC自动机
    int cnt;
    Tree tree[maxn];
    void init() {
        cnt=1;
        memset(tree,0,sizeof(tree));
    }
    void insert(char s[]) {
        int now=root,len=strlen(s);
        for(int i=0; i<len; i++) {
            int j=Index[s[i]+100];
            if(!tree[now].child[j]) {
                tree[++cnt].clear();
                tree[now].child[j]=cnt;
            }
            now=tree[now].child[j];
        }
        tree[now].flag=1;
    }
    void buildfail() { //Bfs构造Fail指针
        queue<int>Q;
        Q.push(root); 
        while(!Q.empty()) {
            int Now=Q.front();
            Q.pop();
            for(int i=0; i<n; i++) {
                int Next=tree[Now].child[i];
                if(Next==0) { //儿子不存在
                    if(tree[tree[Now].fail].child[i])tree[Now].child[i]=tree[tree[Now].fail].child[i];
                    else tree[Now].child[i]=root;
                    continue;
                }
                Q.push(Next);
                int fatherfail=tree[Now].fail; //父亲的失败指针
                while(fatherfail&&!tree[fatherfail].child[i])fatherfail=tree[fatherfail].fail; //寻找可退回点
                if(fatherfail)tree[Next].fail=tree[fatherfail].child[i]; //如果存在满足条件的点则设置失败指针
                else tree[Next].fail=root; //否则指回root
                tree[Next].flag|=tree[tree[Next].fail].flag;
            }
        }
    }
};
Aho_Corasick_Automaton ac;
BigInteger f[105][305],ans=0;
char Word[2005];
int main() {
    scanf("%d%d%d%s",&n,&m,&p,&Word);
    for(int i=0; i<n; i++)Index[Word[i]+100]=i;
    ac.init();
    for(int i=1; i<=p; i++) {
        char s[2005];
        scanf("%s",&s);
        ac.insert(s);
    }
    ac.buildfail();
    f[0][1]=1;
    for(int i=1; i<=m; i++) {
        for(int j=1; j<=ac.cnt; j++) {
            if(ac.tree[j].flag)continue;
            for(int k=0; k<n; k++) {
                if(ac.tree[ac.tree[j].child[k]].flag)continue;
                f[i][ac.tree[j].child[k]]+=f[i-1][j];
            }
        }
    }
    for(int i=1; i<=ac.cnt; i++)
        if(!ac.tree[i].flag)ans+=f[m][i];
    cout<<ans<<endl;
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值