BZOJ1009: [HNOI2008]GT考试(矩阵快速幂+dp+KMP)

1009: [HNOI2008]GT考试

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 4685   Solved: 2916
[ Submit][ Status][ Discuss]

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

【分析】

原本想数位dp,不知道可不可以,不过n的范围........

不看题解不会做,不过还是收获很大。这个题能将矩阵,kmp,dp结合起来,强____*

不吉利串是可以有重复子段的,这个需要KMP的失配函数处理。比如123123,失配函数f[]={0,0,0,0,1,2}

设dp[i][j]表示,前 i 位匹配到 j 位不吉利数字。

由dp[i][j]可以推出dp[i+1][k] (k是 j位数匹配时,可以匹配的短一点的位数)

这样推很麻烦,反过来推,dp[i][j]可以由dp[i-1][k]推出 (k位数失配时,可以倒退到匹配成功 j 个位)


设g[i][j]表示短串 i 位在长串上失配时,可以倒退到匹配成功 j 位的方案数。需要枚举长串当前位置的数字0~9来打g的表。

那么dp[i][j] = sum{ dp[i-1][k]*g[k][j] }(k从0到m-1,k等于m时意味着匹配到了一个短串,故不能记为答案)

这样可以由dp[0][j]递推出dp[n][j]  (j是长串位数,从0到n-1)

但是n太大了,Fortunately,递推式恰好满足矩阵乘法原则:

dp[n] = dp[1] * (矩阵g的n次幂)

【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int mod=1e9+7;
/************** matrix ********************/
struct matrix{
    ll a[21][21]; //begin with 0
    int r,c;
    matrix(int n,int m):r(n),c(m){memset(a,0,sizeof(a));}
    ll* operator[](int x){return a[x];}
    friend matrix operator*(matrix A,matrix B)
    {
        matrix C(A.r,B.c);
        for(int i=0;i<A.r;i++)
            for(int j=0;j<B.c;j++)
                for(int k=0;k<=A.c;k++){
                    C[i][j]+=(A[i][k]*B[k][j])%mod;
                    C[i][j]+=mod;
                    C[i][j]%=mod;
                }
        return C;
    }
 
};
matrix qpow(matrix A,ll m)//方阵A的m次幂
{
    matrix ans(A.r,A.c);
    for(int i=0;i<A.r;i++) ans.a[i][i]=1;//单位矩阵
    while(m)
    {
        if(m%2)ans=ans*A;
        A=A*A;
        m/=2;
    }
    return ans;
}
/************** kmp ********************/
int f[25]; //失配函数
void getfail(char *p)
{
    f[0]=f[1]=0;
    int n=strlen(p);
    for(int i=1;i<n;i++)
    {
        int j=f[i];
        while(j&&p[j]!=p[i])j=f[j];
        if(p[j]==p[i])f[i+1]=j+1;
        else f[i+1]=0;
    }
}
/**************** solve *****************/
///dp[i][j]表前i位匹配上j位的种数 dp[0][0]=1;
int main()
{
    int n,m,k;
    char s[30];
    scanf("%d%d%d%s",&n,&m,&mod,s);
    getfail(s);
    matrix g(m,m); //g[i][j] i位失配时匹配上j个位的种数
    for(int i=0;i<m;i++)
    {
        for(int ch='0';ch<='9';ch++) //枚举当前位,失配到k
        {
            int k=i;
            while(k&&s[k]!=ch)k=f[k];
            if(s[k]==ch)k++;
            if(k<m)//不完全匹配
                g[i][k]++;
        }
    }
    matrix dp(1,m);
    dp[0][0]=1;
    dp=dp*qpow(g,n);
    ll ans=0;
    for(int i=0;i<m;i++)ans=(ans+dp[0][i])%mod;
    cout<<ans<<endl;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值