【JZOJ5078】【GDOI2017第三轮模拟day2】魔法咒语

67 篇文章 1 订阅
15 篇文章 0 订阅

Description

这里写图片描述

Data Constraint

这里写图片描述

Solution

考场上打出了70分,由于没看到100分的长度不超过2,所以与100分失之交臂。
70分显然是打颗AC自动机,在trie树做dp。设f[i][j]表示当前选的字符串长度为i,在trie上第j个点的情况。
我们考虑100分怎么做。看到基本词汇长度长度不超过2,且禁忌词语长度少于100,我们果断想到矩阵乘法。我们把f[i-1],f[i]的方案作为初始矩阵,当前我们要通过乘法转移至f[i+1],f[i],只要想一下构建一个200*200的矩阵就好了。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const ll maxn=2e2+1,mo=1e9+7;
struct code{
    ll a[maxn][maxn];
}c2,b,c1; 
ll f[maxn][maxn],g[maxn][27],p[maxn],c[maxn],v[maxn],d[maxn];
ll n,m,l,i,t,j,k,x,y,z,num,q,bz,ans;
char s[maxn][maxn],s1[maxn];
void make(code x,code y){
    memset(c1.a,0,sizeof(c1.a));ll i,j,k;
    for (i=0;i<=n;i++)
        for (j=0;j<=n;j++){
            for (k=0;k<=n;k++)
                c1.a[i][j]=c1.a[i][j]+x.a[i][k]*y.a[k][j]%mo;
            c1.a[i][j]%=mo;
        }
}
void bfs(){
    int i=0,j=1;
    while (i<j){
        x=v[++i];
        for (k=0;k<26;k++){
            if (!g[x][k]) continue;
            y=g[x][k];z=p[x];c[y]+=c[x];
            while (z && !g[z][k]) z=p[z];
            v[++j]=y;
            if (g[z][k] && x) p[y]=g[z][k],c[y]+=c[p[y]];
        }
    }
}
void dg(){
    while (l>1) d[++d[0]]=l%2,l/=2;
    c1=c2;
    while (d[0]) 
        if (d[d[0]--])make(c1,c1),make(c1,c2);
        else make(c1,c1);
}
int main(){
    freopen("sorcery.in","r",stdin);freopen("sorcery.out","w",stdout);
    scanf("%lld%lld%lld\n",&n,&m,&l);
    for (i=1;i<=n;i++)
        scanf("%s\n",s[i]+1);
    for (i=1;i<=m;i++){
        scanf("%s\n",s1+1);x=0;t=strlen(s1+1);
        for (j=1;j<=t;j++){
            if (!g[x][s1[j]-97]) g[x][s1[j]-97]=++num;
            x=g[x][s1[j]-97];
        }
        c[x]=1;
    }
    bfs();
    if (l<=100){
    f[0][0]=1;
    for (i=0;i<l;i++)
        for (j=0;j<=num;j++){
            if (!f[i][j] || c[j]) continue;
            for (k=1;k<=n;k++){
                t=strlen(s[k]+1);
                if (t+i>l) continue;
                x=j;bz=1;
                for (q=1;q<=t;q++){
                    while (!g[x][s[k][q]-97] && x) x=p[x];
                    if (g[x][s[k][q]-97]){
                        x=g[x][s[k][q]-97];
                        if (c[x]){
                            bz=0;break;
                        }
                    } 
                }
                if (bz) f[i+t][x]=(f[i+t][x]+f[i][j])%mo;
            }
        }
    for (i=0;i<=num;i++)
        ans=(ans+f[l][i])%mo;
    printf("%lld\n",ans);
    }else{
        for (j=0;j<=num;j++){
            if (c[j]) continue;
            for (k=1;k<=n;k++){
                t=strlen(s[k]+1);
                x=j;bz=1;
                for (q=1;q<=t;q++){
                    while (!g[x][s[k][q]-97] && x) x=p[x];
                    if (g[x][s[k][q]-97]){
                        x=g[x][s[k][q]-97];
                        if (c[x]){
                            bz=0;break;
                        }
                    } 
                }
                if (bz){
                    if (t==1) c2.a[j][x]++;
                    else c2.a[j+num+1][x]++;
                }
            }
        }
        for (i=0;i<=num;i++)
                c2.a[i][i+num+1]=1;
        n=num*2+1;
        b.a[0][0]=1;
        dg();
        make(b,c1);
        for (i=0;i<=num;i++)
            ans=(ans+c1.a[0][i])%mo;
        printf("%lld\n",ans);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值