[Luogu P3193] [BZOJ 1009] [HNOI2008]GT考试

探讨了在GT考试中,生成不包含特定不吉利数字的准考证号的算法实现。利用KMP或AC自动机预处理转移边,通过矩阵快速幂计算满足条件的准考证号数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

洛谷传送门
BZOJ传送门

题目描述

阿申准备报名参加 GT 考试,准考证号为 N N 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 M 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 0 0

输入输出格式

输入格式:

第一行输入 N , M , K N,M,K N,M,K.接下来一行输入 M M M位的数。

输出格式:

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

输入输出样例

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

说明

N ≤ 1 0 9 , M ≤ 20 , K ≤ 1000 N\leq10^9,M\leq20,K\leq1000 N109,M20,K1000

解题分析

看到 N ≤ 1 0 9 , M ≤ 20 N\le 10^9, M\le 20 N109,M20, 大概就能猜出这玩意是个矩阵了。

所以用 K M P KMP KMP A C AC AC自动机搞出转移边, m a t [ 0 ] [ i ] mat[0][i] mat[0][i]表示在若干次操作后停留在 i i i号节点的方案数, 跑一边矩阵快速幂即可。

代码如下:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <cctype>
#include <cstdlib>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define ll long long
int MOD, root, cnt, n, m;
int fail[25], son[25][10];
char buf[25];
bool tag[25];
std::queue <int> q;
struct Matrix {ll mat[21][21];} base, unit, start;
IN Matrix operator * (const Matrix &x, const Matrix &y)
{
    Matrix ret;
    R int i, j, k;
    for (i = 0; i < 21; ++i)
    for (j = 0; j < 21; ++j)
    {
        ret.mat[i][j] = 0;
        for (k = 0; k < 21; ++k)
        ret.mat[i][j] += x.mat[i][k] * y.mat[k][j];
        ret.mat[i][j] %= MOD;
    }
    return ret;
}
Matrix fpow()
{
    Matrix ret = unit;
    W (n)
    {
        if (n & 1) ret = ret * base;
        base = base * base, n >>= 1;
    }
    return ret;
}
void insert()
{
    R int len = std::strlen(buf), now = root, id;
    for (R int i = 0; i < len; ++i)
    {
        id = buf[i] - '0';
        if (!son[now][id]) son[now][id] = ++cnt;
        now = son[now][id];
    }
    tag[now] = true;
}
void build()
{
    R int now = root;
    for (R int i = 0; i < 10; ++i) if (son[now][i]) q.push(son[now][i]);
    W (!q.empty())
    {
        now = q.front(); q.pop();
        for (R int i = 0; i < 10; ++i)
        {
            if (son[now][i])
            {
                q.push(son[now][i]);
                fail[son[now][i]] = son[fail[now]][i];
                tag[son[now][i]] |= tag[fail[son[now][i]]];
            }
            else son[now][i] = son[fail[now]][i];
        }
    }
    for (R int i = 0; i <= cnt; ++i)
    {
        for (R int j = 0; j < 10; ++j)
        if (!tag[son[i][j]]) base.mat[i][son[i][j]]++;
    }
    for (R int i = 0; i < 21; ++i) unit.mat[i][i] = 1;
    start.mat[0][0] = 1;
}
int main(void)
{
    scanf("%d%d%d", &n, &m, &MOD);
    scanf("%s", buf); insert(); build();
    int ans = 0;
    Matrix res = start * fpow();
    for (R int i = 0; i <= cnt; ++i) ans = (ans + res.mat[0][i]) % MOD;
    printf("%d", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值