解题 [HNOI2008] GT考试
题目:[HNOI2008] GT考试
阿申准备报名参加 GT 考试,准考证号为 位数,他不希望准考证号上出现不吉利的数字。
他的不吉利数字 有 位,不出现是指 中没有恰好一段等于 , 和 可以为 。
阿申想知道不出现不吉利数字的号码有多少种,输出模 取余的结果。
对于全部数据,,,。
这个题的做法是这样的:
这个题涉及到字符串匹配,题目中的“不出现”,也就代表着不吉利数字无法匹配到 。
1、最暴力的想法是将准考证号枚举出来,然后在进行字符串匹配,这样的复杂度是 的。
2、当使用 KMP 做字符串匹配的时候,我们可以考虑这样一个过程:
- 当 匹配到第 位, 匹配到第 位,在下一个位置发生了失配。
- 然后跳 到下一个状态, 匹配到第 位, 匹配到第 位。
也就是由 的转移。考虑到只需要求方案数,我们设 为当 匹配到第 位, 匹配到第 位的合法的号码的方案数(当然 永远不会等于 )。
但是由 的转移,会存在 或多种。那么考虑枚举字符 ,来得到“由位置 的下一个位置失配到位置 的方案数”,记为 。
for (int k = 0, j; k < m; k ++ )
for (char c = '0'; c <= '9'; c ++ ) {
for (j = nxt[k]; j && a[j + 1] != c; j = nxt[j]);
g[k][j + (a[j + 1] == c)] ++;
}
这样的话,就可以得到状态 的转移 。
那么根据 的定义来看,就可以达到最终答案 ,总时间复杂度为 。
参考代码():
#include <bits/stdc++.h>
using namespace std;
int n, m, K, ans, nxt[21], g[21][21], dp[10010][21];
char a[21];
int main() {
cin >> n >> m >> K >> (a + 1);
for (int i = 2, j = 0; i <= m; i ++ ) {
for (j = nxt[i - 1];j && a[j + 1] != a[i]; j = nxt[j]);
nxt[i] = a[j + 1] == a[i] ? j + 1 : 0;
}
for (int i = 0, j; i < m; i ++ ) {
for (char c = '0'; c <= '9'; c ++ ) {
for (j = i; j && a[j + 1] != c; j = nxt[j]);
g[i][j + (a[j + 1] == c)] ++;
}
}
dp[0][0] = 1;
for (int i = 1; i <= n; i ++ ) {
for (int j = 0; j < m; j ++ ) {
for (int k = 0; k < m; k ++ ) {
dp[i][j] = (dp[i - 1][k] * g[k][j] + dp[i][j]) %K;
}
}
}
for (int i = 0; i < m; i ++ ) ans = (ans + dp[n][i]) %K;
cout << ans << '\n';
return 0;
}
3、优化转移
分析状态 的转移 ,从中可以得到 ,即一个矩乘的式子 。
然后对转移进行矩阵加速幂,时间复杂度为 的。
参考代码():
#include <bits/stdc++.h>
using namespace std;
const int M = 31;
int n, m, K, res, nxt[M];
char s[M];
struct matrix {
int a[M][M], n, m;
matrix() {
n = m = 0;
memset(a, 0, sizeof(a));
}
} F, G;
matrix operator* (const matrix &a, const matrix &b) {
matrix tmp;
tmp.n = a.n, tmp.m = b.m;
for (int i = 0; i < tmp.n; i ++ ) {
for (int j = 0; j < tmp.m; j ++ ) {
for(int k = 0; k < a.m; k ++ ) {
tmp.a[i][j] = (tmp.a[i][j] + a.a[i][k] * b.a[k][j] %K) %K;
}
}
}
return tmp;
}
matrix Power(matrix a, int k) {
matrix ans = a;
for (k --; k; a = a * a, k >>= 1)
if (k & 1) ans = ans * a;
return ans;
}
int main()
{
cin >> n >> m >> K >> (a + 1);
F.n = 1, F.m = G.m = G.n = m;
for (int i = 2, j = 0; i <= m; i ++ ) {
for (j = nxt[i - 1];j && a[j + 1] != a[i]; j = nxt[j]);
nxt[i] = a[j + 1] == a[i] ? j + 1 : 0;
}
for (int i = 0, j; i < m; i ++ ) {
for (char c = '0'; c <= '9'; c ++ ) {
for (j = i; j && a[j + 1] != c; j = nxt[j]);
g[i][j + (a[j + 1] == c)] ++;
}
}
F.a[0][0] = 1;
F = F * Power(G, n);
for (int i = 0; i < m; i ++ ) res = (res + F.a[0][i]) %mod;
cout << res << '\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix