AcWing 1305. GT考试
. 考试
一、题目描述
阿申准备报名参加 考试,准考证号为 位数 ,他不希望准考证号上出现不吉利的数字。
他的不吉利数字 有 位,不出现是指 中没有恰好一段等于 , 和 可以为 。
输入格式
第一行输入 。
接下来一行输入 位的不吉利数字。
输出格式
阿申想知道不出现不吉利数字的号码有多少种,输出模 取余的结果。
数据范围
输入样例:
4 3 100
111
输出样例:
81
二、知识图谱
母题: . 设计密码
-
扩展方式:数据量扩展
. 设计密码中的值最大为,本题的最大可以取到。 -
扩展方式: 不能包含多个字符串
对应题目是: . 修复,可以使用自动机解决。
三、暴力
利用算法求方案数
-
状态表示
设表示到长串的第位,匹配了短串的前缀长度为时的 总方案数答案:
-
状态转移
枚举的每一个数,看在匹配 不能出现的串 位后 再加当前枚举的数能变成匹配几位,如果匹配位了则不合法:- 如果新加的这一位 可以 与 不能出现的串 的下一位匹配,那么就可以转移到 。
- 如果新加的这一位 不能 与 不能出现的串 的下一位匹配,就往回找第一个能匹配的位置,利用的数组往回跳,匹配一个新的前缀。假设找到的位置,那么当前状态就可以转移到。
分代码
复杂度直接爆炸
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, m, mod;
const int N = 1e6 + 10;
int ne[N]; // kmp的ne数组,针对模式串s
char s[N]; // 模式串s
int f[N][22]; // dp数组
//原始版本,就是设计密码那道题的代码,可以过4个点,剩余6个点TLE
int main() {
cin >> n >> m >> mod;
cin >> s + 1;
// kmp
for (int i = 2, j = 0; i <= m; i++) {
while (j && s[j + 1] != s[i]) j = ne[j];
if (s[j + 1] == s[i]) j++;
ne[i] = j;
}
//普通dp
f[0][0] = 1;
for (int k = 0; k < n; k++)
for (int i = 0; i < m; i++)
for (char c = '0'; c <= '9'; c++) {
int j = i;
while (j && s[j + 1] != c) j = ne[j];
if (s[j + 1] == c) j++;
//在kmp过程中进行判断,不能命中m个长度,需要避让
if (j < m) f[k + 1][j] = (f[k + 1][j] + f[k][i]) % mod;
}
int res = 0;
for (int i = 0; i < m; i++) res = (res + f[n][i]) % mod;
printf("%d\n", res);
return 0;
}
四、矩阵优化
时间复杂度
设计密码那道题的,这道题的数据范围有!
看到这个另人发指的数量级,感觉到普通肯定凉凉,一定要用特殊手段!

通过上面的分析,我们根据状态计算可以得到第层和第层之间的关系,即
看着像矩阵,构造一下,令:
递推式:
证明:
结论:具有递推性质!
矩阵实际含义
答: 表示在 和短串匹配长度为的串(后缀) 后面添加一个字符,使新构成的串 和短串匹配长度为(前缀),这样的字符添加方案数。
矩阵求法
答:根据上面的分析可知,矩阵只与不合法串有关,因此矩阵是不变的。根据上面递推式可知:
如何求解数组呢?如果从可以转移到,则让。即让:

求出向量后,最后的答案就是向量中所有的元素之和。
总结
对于所有类似于本题的问题(状态转移方程是线性的)都可以采用快速幂的优化方式
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 25;
int n, m, mod;
char s[N];
int ne[N];
int a[N][N];
//矩阵乘法
void mul(int a[][N], int b[][N], int c[][N]) {
int t[N][N] = {0};
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
t[i][j] = (t[i][j] + (LL)(a[i][k] * b[k][j]) % mod) % mod;
}
memcpy(c, t, sizeof t);
}
int main() {
cin >> n >> m >> mod;
cin >> s + 1;
// kmp
for (int i = 2, j = 0; i <= m; i++) {
while (j && s[j + 1] != s[i]) j = ne[j];
if (s[j + 1] == s[i]) j++;
ne[i] = j;
}
// 初始化A[i][j]
for (int i = 0; i < m; i++)
for (int c = '0'; c <= '9'; c++) {
int j = i;
while (j && s[j + 1] != c) j = ne[j];
if (s[j + 1] == c) j++;
if (j < m) a[i][j]++;
}
// f[0][0]=1 base case
int f[N][N] = {1};
//矩阵快速幂
for (int i = n; i; i >>= 1) {
if (i & 1) mul(f, a, f); // f:基底 a:需要计算快速幂的常数矩阵 f:结果存储
mul(a, a, a); //倍增a
}
int res = 0;
for (int i = 0; i < m; i++) res = (res + f[0][i]) % mod;
printf("%d\n", res);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2018-06-06 IDEA下利用Jrebel插件实现JFinal项目main方法【热加载】
2018-06-06 整理OpenResty+Mysql+Tomcat+JFinal+Cannal+HUI