HNOI 2008 GT考试

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

输入格式
第一行输入N,M,K.接下来一行输入M位的数。 N<= 109 ,M<=20,K<=1000

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

思路
f[i][j] 表示原串第 i 位匹配了不吉利数列(unlu[])第 j 位的方案数
则对于原串中每一个f[i][j],用它更新所有后接0-9数字应该转移到的所有 f[i+1][k]
后接数字要么等于 unlu[j+1] ,那么 k j+1
要么找到前面与其匹配的位置 k

然后用kmp与矩阵快速幂优化即可

感想
实话实说,本题没有想出来
本来想DP方程,然而短路了,看到 n 的范围被吓了一跳,不敢想了
然而是真的DP和矩乘
为避免下次短路,总结一下矩乘的一般使用情况:
1.DP方程的数据很大,要么数学题(我本题居然想了半天容斥)要么矩乘
2.DP方程的递推为线性的,即有关于f[i]=kf[j] k 为常数,不是有关f的东西
3.DP方程的状态可以在矩阵中存下来
4.不是一些奇怪的方程,例如 f[n]=f[n/2] 之类

还有就是 kmp 终于脱离了老人家的从0起步的奇怪做法,终于改为从1起步了

代码

//miaomiao 2017.2.12
#include<cstdio>
#include<cstring>

using namespace std;

#define Set(a, v) memset(a, v, sizeof(a))
#define For(i, a, b) for(int i = (a); i <= (int)(b); i++)
#define M (20+5)

int n, rm, P, unlu[M], f[M];

struct Matrix{
    int m[M][M];
    Matrix(int x=0){
        Set(m, 0); if(!x) return;
        For(i, 0, rm) m[i][i] = 1;
    }

    Matrix operator *(const Matrix &rhs)const{
        Matrix ret;
        For(k, 0, rm-1) For(i, 0, rm-1) if(m[i][k]) For(j, 0, rm-1)
            ret.m[i][j] = (ret.m[i][j]+m[i][k]*rhs.m[k][j]%P)%P;
        return ret;
    }

    Matrix operator ^(int num){
        Matrix ret(1), a = *this;
        while(num){
            if(num&1) ret = ret*a;
            a = a*a; num >>= 1;
        }
        return ret;
    }
}base;

int to[M][10];

inline void getfail(){
    For(i, 1, rm-1){
        int j = f[i];
        while(j && unlu[i+1]!=unlu[j+1]) j=f[j];
        f[i+1] = unlu[i+1]==unlu[j+1]? j+1: 0;
    }    
    For(i, 0, rm-1) For(j, 0, 9){
        to[i][j] = unlu[i+1]==j? i+1: to[f[i]][j];
        base.m[i][to[i][j]]++;
    }
}

int main(){
    scanf("%d%d%d", &n, &rm, &P);
    For(i, 1, rm) scanf("%1d", &unlu[i]);
    getfail();

    Matrix a; a.m[0][0] = 1;
    a = a*(base^n);
    int sum = 0;
    For(i, 0, rm-1) sum += a.m[0][i];
    printf("%d\n", sum%P);

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值