洛谷-3193 [HNOI2008]GT考试

题目描述
阿申准备报名参加 GT 考试,准考证号为 N 位数 X 1 , X 2 … X n ( 0 ≤ X i ≤ 9 ) X_1,X_2…X_n(0\le X_i\le9) X1,X2Xn(0Xi9),他不希望准考证号上出现不吉利的数字。 他的不吉利数学 A 1 , A 2 … A m ( 0 ≤ A i ≤ 9 ) A_1,A_2…A_m(0\le A_i\le 9) A1,A2Am(0Ai9)​ 有 M 位,不出现是指 X 1 , X 2 … X n X_1,X_2…X_n X1,X2Xn中没有恰好一段等于 A 1 , A 2 … A m A_1,A_2…A_m A1,A2Am​, A 1 A_1 A1​ 和 X 1 X_1 X1​ 可以为 0
输入格式
第一行输入N,M,K.接下来一行输入M位的数。
输出格式
阿申想知道不出现不吉利数字的号码有多少种,输出模 K 取余的结果。

输入输出样例
输入 #1
4 3 100
111

输出 #1
81

说明/提示
N ≤ 1 0 9 , M ≤ 20 , K ≤ 1000 N≤10^9,M≤20,K≤1000 N109,M20,K1000

解释:是 D P DP DP,我们设 d p [ i ] [ j ] : [ 1 , i ] dp[i][j]:[1,i] dp[i][j][1,i]中与不吉利串匹配前 j j j的个数,那么
d p [ i ] [ j ] = ∑ k = 1 m − 1 d p [ i − 1 ] [ k ] ∗ g k , j dp[i][j]=\sum_{k=1}^{m-1}dp[i-1][k]*g_{k,j} dp[i][j]=k=1m1dp[i1][k]gk,j,其中 g k , j g_{k,j} gk,j是匹配不吉利串前j个加入一个字符后变成前 k k k个的个数, g k , j g_{k,j} gk,j的化我们可以通过KMP的next数组计算出来,然后观察 d p dp dp方程可以转化成矩阵乘,所以我们可以用矩阵快速幂加速

#include<cstdio>
#include<iostream>
#include<cstring>
#define ll long long
using namespace std;
#define N 30
int mod=0;
class Matrix{
    public:
        ll a[N][N],n;
        Matrix(ll _n){
            n=_n;
            memset(a,0,sizeof(a));
            for(int i=0;i<n;i++) a[i][i]=1%mod;
        }
        ll get(int x,int y){
            return a[x][y];
        }
        void set(int x,int y,int val){
            a[x][y]=val%mod;
        }
        void mul(Matrix &b){
            ll c[N][N]={0};
            for(int i=0;i<n;i++){
                for(int j=0;j<n;j++){
                    for(int k=0;k<n;k++){
                        c[i][j]+=a[i][k]*b.a[k][j]%mod;
                        c[i][j]%=mod;
                    }
                }
            }
            for(int i=0;i<n;i++)
                for(int j=0;j<n;j++) a[i][j]=c[i][j];
        }
        void pow(ll k){
            Matrix ret(n);
            while(k){
                if(k&1){
                    ret.mul(*this);
                }k>>=1;
                this->mul(*this);
            }
            for(int i=0;i<n;i++)
                for(int j=0;j<n;j++) a[i][j]=ret.a[i][j]%mod;
        }
        void display(){
            for(int i=0;i<n;i++){
                printf("%lld",a[i][0]);
                for(int j=1;j<n;j++){
                    printf(" %lld",a[i][j]);
                }
                printf("\n");
            }
        }
};
int n=0,m=0;
char str[33];
int g[30][30]={0};
int next[33]={0};
void cal_next(){
    next[1]=0;
    next[0]=-1;
    for(int i=2,j=0;i<=m;i++){
        while(j>=0&&str[i]!=str[j+1])  j=next[j];
        next[i]=++j;
    }
}
int main(){
    cin>>n>>m>>mod;
    Matrix mk(m);
    cin>>(str+1);
    cal_next();
    for(int i=0;i<m;i++){
        for(char ch='0';ch<='9';ch++){
            int j=i;
            while(j>=0&&ch!=str[j+1]){
                j=next[j];
            }
            g[i][j+1]++;
        }
    }
    for(int i=0;i<m;i++){
        for(int j=0;j<m;j++){
            mk.a[i][j]=g[j][i];
        }
    }
    mk.pow(n-1);
    ll ret=0;
    for(int i=0;i<m;i++){
        ret+=9*mk.a[i][0]%mod+mk.a[i][1]%mod;
        ret%=mod;
    }
    cout<<ret<<endl;
    return 0;
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值