【BZOJ1009】GT考试(KMP算法,矩阵快速幂,动态规划)

232 篇文章 0 订阅
94 篇文章 0 订阅

题面

BZOJ

题解

看到这个题目
化简一下题意
长度为 n 的,由09组成的字符串中
不含串 s 的串的数量有几个

很显然,如果组成的字符串和s串做 KMP 的匹配的话
是不能匹配到最后一位的

所以,我们想到一个很显然的方程
f[i][j] 表示当前做了第 i 位,在s串中匹配到了第 j
每次枚举下一位放的数字
以及每一位的位置
相当于做KMP的匹配
然后进行转移

所以,我们可以写出一个暴力

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int f[2000][30];
int nt[30],n,m,K;
char s[30];
void Get_Next(char *s)
{
    int l=strlen(s+1);
    nt[1]=0;
    for(int i=2;i<=l;++i)
    {
        int t=nt[i-1];
        while(t&&s[i]!=s[t+1])t=nt[t];
        if(s[i]==s[t+1])t+=1;
        nt[i]=t;
    }
}
int main()
{
    n=read();m=read();K=read();
    scanf("%s",s+1);
    Get_Next(s);
    f[0][0]=1;
    for(int i=0;i<n;++i)
    {
        for(int j='0';j<='9';++j)
        {
            for(int k=0;k<m;++k)
            {
                int t=k;
                while(t&&s[t+1]!=j)t=nt[t];
                if(j==s[t+1])t++;
                (f[i+1][t]+=f[i][k])%=K;
            }
        }
    }
    int ans=0;
    for(int i=0;i<m;++i)ans+=f[n][i];
    printf("%d\n",ans%K);
    return 0;
}

n 的范围有109
不可能是 O(n) 解了
我们发现每次匹配的转移关系是一定的
所以可以用矩阵快速幂来优化 dp 转移

复杂度为 O(n+m3logn)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
inline int read()
{
    int x=0,t=1;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int nt[30],n,m,K;
char s[30];
void Get_Next(char *s)
{
    int l=strlen(s+1);
    nt[1]=0;
    for(int i=2;i<=l;++i)
    {
        int t=nt[i-1];
        while(t&&s[i]!=s[t+1])t=nt[t];
        if(s[i]==s[t+1])t+=1;
        nt[i]=t;
    }
}
struct Dalao
{
    int s[30][30];
    void init()
        {
            memset(s,0,sizeof(s));
            for(int i=0;i<m;++i)s[i][i]=1;
        }
    void clear(){memset(s,0,sizeof(s));}
}G;
Dalao operator*(Dalao a,Dalao b)
{
    Dalao ret;ret.clear();
    for(int i=0;i<m;++i)
        for(int j=0;j<m;++j)
            for(int k=0;k<m;++k)
                (ret.s[i][j]+=a.s[i][k]*b.s[k][j]%K)%=K;
    return ret;
}
Dalao fpow(Dalao a,int b)
{
    Dalao s;s.init();
    while(b){if(b&1)s=s*a;a=a*a;b>>=1;}
    return s;
}
int main()
{
    n=read();m=read();K=read();
    scanf("%s",s+1);
    Get_Next(s);
    for(int j='0';j<='9';++j)
    {
        for(int k=0;k<m;++k)
        {
            int t=k;
            while(t&&s[t+1]!=j)t=nt[t];
            if(j==s[t+1])t++;
            G.s[k][t]++;
        }
    }
    G=fpow(G,n);
    int ans=0;
    for(int i=0;i<m;++i)ans=(ans+G.s[0][i])%K;
    printf("%d\n",ans%K);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值