DestinHistoire

 

BZOJ-1009 [HNOI2008]GT考试(矩阵快速幂加速dp+KMP)

题目描述

  准考证号为 \(n\) 位数 \(X_1,X_2,\cdots,X_n(0\leq X_i\leq 9)\),不希望准考证号上出现不吉利的数字。不吉利数字 \(A_1,A_2,\cdots,A_m(0\leq A_i\leq 9)\)\(m\) 位,不出现是指 \(X_1,X_2,\cdots,X_n\) 中没有恰好一段等于 \(A_1,A_2,\cdots,A_m\)\(A_1\)\(X_1\)可以为 \(0\)。求合法方案数,答案对 \(p\) 取模。

  数据范围:\(n\leq 10^9,m\leq 20,p\leq 1000\)

分析

  设 \(dp[i][j]\) 表示准考证的前 \(i\) 位数中的最后 \(j\) 位与不吉利的数字的前 \(j\) 位相同时,前 \(i\) 位的合法方案数,且 \(dp[i][j]\) 的每种方案都不含长度大于 \(j\) 且与不吉利数字的前缀相同的后缀(为了避免重复)。则答案为:\(dp[n][0]+dp[n][1]+\cdots+dp[n][m-1]\)

  考虑如何进行状态转移,\(dp[i][j]\) 只能由 \(dp[i-1][k]\) 转移过来,相当于填完第 \(i-1\) 位后,长为 \(k\) 的后缀后面新添加一位 \(num\),此时这个有 \(i\) 位的数字与不吉利数字前缀相同的最长的后缀的长度为 \(j\)

  状态转移方程为:

\[dp[i][j]=\sum_{k=0}^{m-1}dp[i-1][k]\times f[k][j] \]

  上式的 \(f[k][j]\) 表示当前准考证的长为 \(k\) 的后缀已经匹配了不吉利数字长为 \(k\) 的前缀,有多少种添加一个数字 \(num\) 的方法,能使匹配长度变为 \(j\)

  举个例子:假设不吉利数字是 \(123124\),则 \(dp[i][3]=dp[i-1][2]+dp[i-1][5]\),因为 \(dp[i-1][2]\) 的后缀\(\cdots12\) 不能是 \(\cdots12312\),所以还需要 \(dp[i-1][5]\) 来补充;假设不吉利数字是 \(123123\),则 \(dp[i][3]=dp[i-1][2]\),因为 \(dp[i][3]\) 末尾的 \(\cdots123\) 不能是 \(\cdots123123\)

  因为我们现在已经知道不吉利数字是什么,所以 \(f[k][j]\) 矩阵是固定的,可以用 \(\text{KMP}\) 算法预处理 \(\text{Next}\) 数组,然后枚举长度 \(k\) 和添加的数字 $num $,沿着 \(\text{Next}\) 数组往前跳找到来找到能转移到的 \(j\),从而预处理出 \(f[k][j]\) 数组。

  可以发现这个状态转移方程和矩阵乘法的的式子非常像,用矩阵快速幂加速 $dp $ 即可,时间复杂度 \(O(m^3\log n)\)

代码

#include<bits/stdc++.h>
using namespace std;
int n,m,mod,Next[30];
char s[30];
struct matrix
{
    int mat[30][30];
    matrix()
    {
        memset(mat, 0, sizeof(mat));
    }
}A;
matrix mul(matrix A,matrix B)
{
    matrix ans;
    for(int i=0;i<m;i++)
        for(int j=0;j<m;j++)
            for(int k=0;k<m;k++)
                ans.mat[i][j]=(ans.mat[i][j]+A.mat[i][k]*B.mat[k][j])%mod;
    return ans;
}
matrix matrix_pow(matrix a,int b)
{
    matrix ans;
    for(int i=0;i<m;i++)
        for(int j=0;j<m;j++)
            ans.mat[i][j]=(i==j);
    while(b)
    {
        if(b&1)
            ans=mul(ans,a);
        a=mul(a,a);
        b>>=1;
    }
    return ans;
}
void get_next()
{
    Next[1]=0;
    int j=0;
    for(int i=2;i<=m;i++)
    {
        while(j>0&&s[i]!=s[j+1])
            j=Next[j];
        if(s[i]==s[j+1])
            j++;
        Next[i]=j;
    }
}
matrix KMP()
{
    get_next();
    matrix ans;
    for(int i=0;i<=m-1;i++)
    {
        for(char num=0;num<=9;num++)
        {
            int j=i;
            while(j>0&&num!=(int)(s[j+1]-'0'))
                j=Next[j];
            if(num==(int)(s[j+1]-'0'))
                j++;
            ans.mat[i][j]++;
        }
    }
    return ans;
}
matrix f;
int main()
{
    cin>>n>>m>>mod;
    scanf("%s",s+1);
    f=matrix_pow(KMP(),n);
    int ans=0;
    for(int i=0;i<=m-1;i++)
        ans=(ans+f.mat[0][i])%mod;
    cout<<ans<<endl;
    return 0;
}

posted on 2020-11-24 18:57  DestinHistoire  阅读(70)  评论(0)    收藏  举报

导航