码迷,mamicode.com
首页 > 其他好文 > 详细

#4302. 魔法咒语

时间:2019-04-07 22:26:49      阅读:145      评论:0      收藏:0      [点我收藏+]

标签:front   std   line   表达   nan   har   语言   拆点   词汇   

题意
内存限制:256 MiB
时间限制:1000 ms
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$ 的结果。

对于 $60\%$ 的数据 $1 \le N,M \le 50,L \le 100$
对于另 $40\%$ 数据基本词汇长度不超过 $2$ , $L \le 10^8$

题解
考虑对忌讳词语建AC自动机,对于前60分,我们可以设 $f_{i,j}$ 表示从AC自动机的根(我的根为0)出发,走到 $i$ 这个点用了 $j$ 步且不经过忌讳词汇的方案数
则 $f_{i_j}→f_{i‘,j+len_k}$ ,需要保证转移合法,即从 $i$ 利用基本词汇 $k$ 走到 $i‘$ 不经过忌讳词汇
对于后40分,发现 $len_k \leq 2$ ,所以可以把拆点,把 $x$ 点拆成 $x_1$ 和 $x_2$ 且 $(x_2,x_1)=1$ (单向边
若从 $x$ 用了 $k$ 这个单词走到了 $y$ ,对 $len_k$ 进行分讨
若 $len_k=1$ ,则 $(x_1,y_1)=1$
若 $len_k=2$ ,则 $(x_1,y_2)=1$
利用矩乘计算答案即可

代码

#include <bits/stdc++.h>
#define I inline
using namespace std;
const int N=205,P=1e9+7;
int tr[N][26],t,n,m,L,Len[N];
int ans,f[N][N],fa[N],d[N][2],c;
char h[N][N];queue<int>q;bool e[N];
I void insert(){
    int p=0,l=strlen(h[0]);
    for (int k,i=0;i<l;i++){
        k=h[0][i]-a;
        if (!tr[p][k])
            tr[p][k]=++t;
        p=tr[p][k];
    }e[p]=1;
}
I void build(){
    for (int i=0;i<26;i++)
        if (tr[0][i]) q.push(tr[0][i]);
    while(!q.empty()){
        int k=q.front();q.pop();
        for (int i=0;i<26;i++)
            if (tr[k][i])
                fa[tr[k][i]]=tr[fa[k]][i],
                e[tr[k][i]]=e[tr[k][i]]|e[fa[tr[k][i]]],
                q.push(tr[k][i]);
            else tr[k][i]=tr[fa[k]][i];
    }
}
I int query(int u,int p){
    if (e[p]) return -1;
    for (int i=0;i<Len[u];i++){
        p=tr[p][h[u][i]-a];
        if (e[p]) return -1;
    }
    return p;
}
struct E{int p[N][N];}a,s,g;
I E W(E A,E B){
    for (int i=1;i<=c;i++)
        for (int j=1;j<=c;j++) g.p[i][j]=0;
    for (int i=1;i<=c;i++) for (int l=1;l<=c;l++)
        if (B.p[i][l]) for (int j=1;j<=c;j++)
            (g.p[i][j]+=1ll*A.p[l][j]*B.p[i][l]%P)%=P;
    return g;
}
int main(){
    scanf("%d%d%d",&n,&m,&L);
    for (int i=1;i<=n;i++)
        scanf("%s",h[i]),Len[i]=strlen(h[i]);
    for (int i=1;i<=m;i++) scanf("%s",h[0]),insert();
    build();if (L<=100){
        f[0][0]=1;for (int i=0;i<=L;i++)
            for (int j=0;j<=t;j++) if (f[j][i])
                for (int l,k=1;k<=n;k++)
                    if (Len[k]+i<=L && ~(l=query(k,j)))
                        (f[l][i+Len[k]]+=f[j][i])%=P;
        for (int i=0;i<=t;i++) (ans+=f[i][L])%=P;goto out;
    }
    for (int i=0;i<=t;i++)
        d[i][0]=++c,d[i][1]=++c,a.p[c-1][c]++;
    for (int i=0;i<=t;i++)
        for (int l,j=1;j<=n;j++)
            if (~(l=query(j,i)))
                a.p[d[i][Len[j]-1]][d[l][0]]++;
    for (int i=1;i<=c;i++) s.p[i][i]++;
    for (;L;L>>=1,a=W(a,a)) if (L&1) s=W(s,a);
    for (int i=0;i<=t;i++) (ans+=s.p[1][d[i][0]])%=P;
    out:;return printf("%d\n",ans),0;
}

 

#4302. 魔法咒语

标签:front   std   line   表达   nan   har   语言   拆点   词汇   

原文地址:https://www.cnblogs.com/xjqxjq/p/10667117.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!