bzoj4861 [Beijing2017]魔法咒语(AC+矩阵优化dp)

33 篇文章 0 订阅
24 篇文章 0 订阅

Description

Chandra 是一个魔法天才。

从一岁时接受火之教会洗礼之后,Chandra 就显示出对火元素无与伦比的亲和力,轻而易举地学会种种晦涩难解的法术。这也多亏 Chandra 有着常人难以企及的语言天赋,让她能轻松流利地说出咒语中那些极其拗口的魔法词汇。

直到十四岁,开始学习威力强大的禁咒法术时,Chandra 才遇到了障碍。

根据火之魔法规则,禁咒的构成单位是 N 个基本词汇。施法时只要凝聚精神力,说出一段用这些词语组成的长度恰好等于 L 的语言,就能释放威力超乎想象的火法术。过去的魔法师们总结了几种表达起来最连贯的组合方式,方便施法者以最快语速完成法术。

但具有魔法和语言双重天才的 Chandra 不满足于这几种流传下来的禁咒,因为她可以毫无困难地说出普通人几乎不可能表达的禁咒语句。然而,在实际施法时,Chandra 发现有些自创禁咒念出后不但没有预期效果,反而会使自己的精神力迅速枯竭,十分难受。

这个问题令 Chandra 万分不解。她大量阅读典籍,到处走访魔法学者,并且不顾精神折磨一次又一次尝试新咒语,希望找出问题的答案。

很多年过去了,在一次远古遗迹探险中,Chandra 意外闯进了火之神艾利克斯的不知名神殿。根据岩土特征分析,神殿应该有上万年的历史,这是极其罕见的。Chandra 小心翼翼地四处探索,沿着魔力流动来到一间密室。她看见密室中央悬浮着一本书籍。在魔法保护下书籍状况完好。精通上古语言的 Chandra 读过此书,终于解开了多年的困惑。

禁咒法术之所以威力强大,是因为咒语借用了火之神艾利克斯的神力。这本书里记载了艾利克斯生平忌讳的 M 个词语,比如情敌的名字、讨厌的植物等等。使用禁咒法术时,如果语言中含有任何忌讳词语,就会触怒神力而失效,施法者也一并遭受惩罚。

例如,若 ”banana” 是唯一的忌讳词语,“an”、”ban”、”analysis” 是基本词汇,禁咒长度须是 11,则“bananalysis” 是无效法术,”analysisban”、”anbanbanban”是两个有效法术。注意:一个基本词汇在禁咒法术中可以出现零次、一次或多次;只要组成方式不同就认为是不同的禁咒法术,即使书写形式相同。

谜题破解,Chandra 心情大好。她决定计算一共有多少种有效的禁咒法术。

由于答案可能很大,你只需要输出答案模 1,000,000,007 的结果。

Input

第一行,三个正整数 N, M, L。
接下来 N 行,每行一个只含小写英文字母的字符串,表示一个基本词汇。
接下来 M 行,每行一个只含小写英文字母的字符串,表示一个忌讳词语。
对于60%的数据1<=N,M<=50,L<=100
对于另40%数据基本词汇长度不超过2,L<=10^8

Output

仅一行,一个整数,表示答案(模 10^9+7)。

Sample Input

4 2 10
boom
oo
ooh
bang
ob
mo

Sample Output

14

【样例解释 】

有效的禁咒法术共有 14 种:boom/bang/oo,oo/oo/oo/oo/oo,oo/oo/ooh/ooh,
oo/ooh/oo/ooh, oo/ooh/ooh/oo, ooh/oo/oo/ooh, ooh/oo/ooh/oo,
ooh/ooh/boom, ooh/ooh/oo/oo, ooh/ooh/bang, ooh/bang/ooh,
bang/oo/oo/oo, bang/ooh/ooh, bang/bang/oo。



分析:
很显然,这道题就是AC自动机上的DP
当数据小的时候,我们可以直接DP;数据大我们就用矩阵加速

一开始

我把所有的基本串建了AC自动机
在自动机上跑一遍,找到其中是禁忌串的结点(这些结点是不能到达的)
设计状态:f[i][j],表示第i位,到了AC自动机第j个结点
但是转移的时候遇到了问题:不能保证出现完整的基本串
以前我们加了一维解决这个问题
但是这道题的数据范围太大了,我们不能这样解决问题

正解 . L<=100

网上的解法都是把禁忌串扔到一棵AC自动机上
在AC自动机上跑一遍,记录一个数组:
to[i][j],表示从自动机上的结点i开始,能转移到第j个基本串(在AC自动机上的位置是第to[i][j]个结点)

设计状态:f[i][j],表示第i位,到了AC自动机第j个结点

咦,这个状态不是和以前一样吗
是啊,但是我们之前预处理出了一个to数组,这就有很大的作用了

状态转移:

  • 第一维枚举长度i
  • 第二维枚举AC自动机上的结点j
  • 第三维枚举to[j][k],这样我们转移的时候我们直接转移一整个字符串,这样就能解决我们一开始的问题:保证出现完整的基本串
正解 . L>100

我们还是处理出这个神奇的to数组
我们构造一个2top*2top的矩阵H[i][j],表示AC自动机上的结点i,经过某个基本串后,能够转移到第j个结点

  • 第一维枚举AC自动机上的结点i
  • 第二维枚举to[i][j]
    • 如果基本串的长度为1,H[i][to[i][j]]++
    • 如果基本串的长度为2,H[i+top+1][to[i][j]]++

Q.

说实话我不太理解为什么要用禁忌串建AC自动机

tip

数组开小了,WA了好长时间。。。

状态不好,代码写的不好↓

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#define ll long long

using namespace std;

const int N=110;
const ll p=1e9+7;
int n,m,L,ch[N][27],top=0,fail[N],to[N][N],la[N],lb[N],mn;
char basic[N][N],avoid[N][N];
bool ed[N];
ll f[N][N];

struct node{
    ll H[210][210];

    node operator *(const node &a)const
    {
        node ans;
        for (int i=0;i<=mn;i++)
            for (int j=0;j<=mn;j++)
            {
                ans.H[i][j]=0;
                for (int k=0;k<=mn;k++)
                    ans.H[i][j]=(ans.H[i][j]+(ll)H[i][k]*a.H[k][j])%p;
            }
        return ans;
    }

    void clear()
    {
        memset(H,0,sizeof(H));
    }

    node KSM(int b)
    {
        node ans=(*this),a=(*this);
        b--;
        while (b)
        {
            if (b&1) ans=ans*a;
            b>>=1;
            a=a*a;
        }
        return ans;
    }
};

void build(char s[])
{
    int now=0;
    for (int i=1;i<=strlen(s+1);i++)
    {
        int x=s[i]-'a';
        if (!ch[now][x]) ch[now][x]=++top;
        now=ch[now][x];
    }
    ed[now]=1;
}

void makefail()
{
    queue<int> q;
    for (int i=0;i<26;i++) 
        if (ch[0][i]) q.push(ch[0][i]);
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        for (int i=0;i<26;i++)
        {
            if (!ch[now][i])
            {
                ch[now][i]=ch[fail[now]][i];
                continue;
            }
            fail[ch[now][i]]=ch[fail[now]][i];
            ed[ch[now][i]]|=ed[fail[ch[now][i]]];
            q.push(ch[now][i]);
        }
    }
}

void solve1()
{
    f[0][0]=1;
    for (int i=0;i<L;i++)
        for (int j=0;j<=top;j++) if (f[i][j])
            for (int k=1;k<=n;k++) if (to[j][k]!=-1&&lb[k]+i<=L)
                f[i+lb[k]][to[j][k]]=(f[i+lb[k]][to[j][k]]+f[i][j])%p;
    ll ans=0;
    for (int i=0;i<=top;i++) ans=(f[L][i]+ans)%p;
    printf("%lld\n",ans);
}

void solve2()
{
    node H;
    mn=top*2+1;
    for (int i=0;i<=top;i++)
    {
        for (int j=1;j<=n;j++)
            if (to[i][j]!=-1)
                if (strlen(basic[j]+1)==1)
                    H.H[i][to[i][j]]++;
                else 
                    H.H[i+top+1][to[i][j]]++;
        H.H[i][i+top+1]++;
    }
    node an=H.KSM(L);
    ll ans=0;
    for (int i=0;i<=top;i++)
        ans=(ans+an.H[0][i])%p;
    printf("%lld\n",ans);
}

int main()
{
    scanf("%d%d%d",&n,&m,&L);
    for (int i=1;i<=n;i++)
        scanf("%s",basic[i]+1),lb[i]=strlen(basic[i]+1);
    for (int i=1;i<=m;i++)
        scanf("%s",avoid[i]+1),la[i]=strlen(avoid[i]+1);
    for (int i=1;i<=m;i++) build(avoid[i]);
    makefail();

    for (int i=0;i<=top;i++)   
        for (int j=1;j<=n;j++)
        {
            int now=i,k=1,len=strlen(basic[j]+1);
            for (;k<=len&&!ed[now];now=ch[now][basic[j][k]-'a'],k++);
            if (ed[now]) to[i][j]=-1;
            else if (k==len+1) to[i][j]=now;
            else to[i][j]=-1;
        } 

    if (L<=100) solve1();
    else solve2();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值