BZOJ1009 - [HNOI2008]GT考试

原题链接

Description

给出一个长度为 m(m20) 的数字串,求不包含这个数字串的 n(n109) 位数字串有多少个。答案对 k(k103) 取模。

Solution

矩阵乘法优化DP。
f[i][j] 表示末尾至少再补上 j 个数字才能形成模式串的i位数字串的个数。举例来说,模式串是12345,则f[i][1]表示以1234结尾的 i 位数字串的个数(再补一个数5),f[i][2]表示以123结尾的 i 位数字串的个数(再补两个数45)…
注意至少!比如对于模式串12123,结尾是1212的串应该只属于f[i][1]而不属于 f[i][3]

那么,如何构造由 [f[i][1]...f[i][m]] 转移为 [f[i+1][1]...f[i+1][m]] 的矩阵呢?
i+1 位数字串是由 i 位数字串在末尾加上一位数字得到的,所以我们可以通过枚举这一位上的数字来算出这个矩阵。再次举例,模式串121的转移矩阵为:

010011989
A11=0 表示以12结尾的串,无论在末尾加上什么数也不能变成以12结尾的串。
A12=0 表示以12结尾的串,无论在末尾加上什么数也不能变成以1结尾的串。
A13=9 表示以12结尾的串,在末尾加上023456789这九个数能够变成不以112结尾的串。
A21=1 表示以1结尾的串,在末尾加上2这个数能够变成以12结尾的串。
A22=1 表示以1结尾的串,在末尾加上1这个数能够变成以1结尾的串。
A23=8 表示以1结尾的串,在末尾加上03456789这八个数能够变成不以112结尾的串。

可以看到第一行元素和是9,其他行元素和是10。因为差1位就形成模式串的话,补上那位就不合法啦。
初始状态 [00...1] ,再乘以转移矩阵 T n次方,答案就是最终得到的矩阵各元素之和。

时间复杂度 O(m3logn)

Code

//[HNOI2008]GT考试
#include <cstdio>
#include <cstring>
int n,m,P; char s[30],s0[30];
struct matrix
{
    int row,col,v[30][30];
    matrix operator *(matrix b)
    {
        int p=row,q=col,r=b.col;
        matrix c; c.row=p,c.col=r;
        for(int i=1;i<=p;i++)
            for(int j=1;j<=r;j++)
            {
                c.v[i][j]=0;
                for(int k=1;k<=q;k++)
                    c.v[i][j]+=v[i][k]*b.v[k][j],c.v[i][j]%=P;
            }
        return c;
    }
}x,y;
int pre(int L)
{
    for(int i=1;i<=L;i++)
    {
        int j=i;
        while(s[j-i+1]==s0[j]&&j<=L) j++;
        if(j==L+1) return m-(L-i+1); 
    }
    return m;
}
matrix pow(matrix a,int b)
{
    matrix res; res.row=res.col=a.row;
    for(int i=1;i<=res.row;i++)
        for(int j=1;j<=res.col;j++)
            res.v[i][j]=(i==j);
    matrix t=a;
    while(b>0)
    {
        if(b&1) res=res*t;
        t=t*t; b>>=1;
    }
    return res;
}
int main()
{
    scanf("%d%d%d",&n,&m,&P);
    scanf("%s",s+1);
    x.row=x.col=m;
    for(int i=1;i<=m;i++)
    {
        memset(s0,0,sizeof s0);
        for(int j=1;j<=m-i;j++) s0[j]=s[j];
        for(char j='0';j<='9';j++) s0[m-i+1]=j,x.v[i][pre(m-i+1)]++;
    }
    x.v[1][0]=0;
    x=pow(x,n);
    y.row=1,y.col=m,y.v[1][m]=1; y=y*x;
    int ans=0;
    for(int i=1;i<=m;i++) ans+=y.v[1][i];
    printf("%d\n",ans%P);
    return 0;
}

P.S.

再这样咸鱼…会死…

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值