[bzoj1009][kmp][DP][矩阵乘法]GT考试

123 篇文章 1 订阅
109 篇文章 4 订阅

Description

 阿申准备报名参加GT考试,准考证号为N位数X1X2….Xn(0<=Xi<=9),他不希望准考证号上出现不吉利的数字。
他的不吉利数学A1A2…Am(0<=Ai<=9)有M位,不出现是指X1X2…Xn中没有恰好一段等于A1A2…Am.
A1和X1可以为 0

Input

  第一行输入N,M,K.接下来一行输入M位的数。 N<=10^9,M<=20,K<=1000

Output

  阿申想知道不出现不吉利数字的号码有多少种,输出模K取余的结果.

Sample Input

4 3 100
111

Sample Output

81

题解

好久之前的题了。。
第一眼看这题。。是不是不知所措!是你就对了,因为我就是这样的
首先观察一下题目,简化题意就是一个给定长度的串,要求这个串里面不包含另一个给出的串,求这种串有多少个。Kmp!
暴力就可以枚举所有串的情况然后kmp。。当然这样会爆炸
高级一点的,我们来个Dp
设f[i][j]为 长度为i的母串的后缀与子串匹配到了j个长度的方案数
很明显,答案就是Sigma(f[n][0]~f[n][m-1])对吧
再给出一个数组w[i][j],表示在子串长度为i的前缀后加一个数,使这个前缀+这个数所组成的串与子串从头开始所能匹配的长度为j的方案数
转移方程就是f[i][j]*w[j][k]->f[i+1][k]
这样的复杂度就是n*m^2的,时间空间都会炸
考虑优化,转移方程中可以看出f[i+1]只会与f[i]有关,那么可以滚动。空间过得去时间过不去而已
你既然都想到空间优化居然想不到时间可以矩乘
每个f[i][j]都要乘w[j]这个数组给后面做贡献,且w数组是不变的。那么就矩乘优化啦!
代码中可以直接用转移矩阵代替w矩阵,因为转移矩阵其实就是w矩阵调转一个方向。至于为什么这样我推荐你手推一下矩阵
有个小槽点,w[i][m]这个状态是不能继承的,因为这样母串中一定会出现子串。而w[i][0]这个状态可继承。所以矩乘的时候枚举为0~m-1

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
struct matrix
{
    int m[25][25];
    matrix(){memset(m,0,sizeof(m));}
}st;
int n,m,Mod;
matrix multi(matrix u,matrix v,int n,int m,int p)
{
    matrix ret;
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            for(int k=0;k<p;k++)
                ret.m[i][k]=(ret.m[i][k]+u.m[i][j]*v.m[j][k])%Mod;
    return ret;
}
matrix pow_mod(matrix u,int b)
{
    matrix ans;
    for(int i=0;i<m;i++)ans.m[i][i]=1;
    while(b)
    {
        if(b%2==1)ans=multi(ans,u,m,m,m);
        u=multi(u,u,m,m,m);b/=2;
    }
    return ans;
}
int p[25];//前缀为i位加一位能转移到j位 方案数
char s[30];
void kmp()
{
    int len=strlen(s+1);
    p[1]=0;int j;
    for(int i=2;i<=len;i++)
    {
        j=p[i-1];
        while(s[j+1]!=s[i] && j>0)j=p[j];
        if(s[i]==s[j+1])j++;
        p[i]=j;
    }
    for(int i=0;i<len;i++)
        for(int y=0;y<=9;y++)
        {
            int j=i;
            while(j>0 && s[j+1]-'0'!=y)j=p[j];
            if(s[j+1]-'0'==y)j++;
            st.m[j][i]=(st.m[j][i]+1)%Mod;
        }
}
int main()
{
    scanf("%d%d%d",&n,&m,&Mod);
    scanf("%s",s+1);
    kmp();
    st=pow_mod(st,n);
    int ans=0;
    for(int i=0;i<m;i++)ans=(ans+st.m[i][0])%Mod;
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值