GT考试不吉利数字分析
Создано: 5 февраля 2025 г.
Создано: 5 февраля 2025 г.
阿申准备报名参加 GT 考试,准考证号为 n
位数 X1X2⋯Xn
,他不希望准考证号上出现不吉利的数字。
他的不吉利数字 A1A2⋯Am
有 m
位,不出现是指 X1X2⋯Xn
中没有恰好一段等于 A1A2⋯Am
,A1
和 X1
可以为 0
。
输入格式
第一行输入 n,m,K
。
接下来一行输入 m
位的不吉利数字。
输出格式
阿申想知道不出现不吉利数字的号码有多少种,输出模 K
取余的结果。
数据范围
0≤Xi,Ai≤9
,
1≤n≤109
,
1≤m≤20
,
2≤K≤1000
输入样例:
4 3 100
111
输出样例:
81 代码:#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 25;
int n, m, mod;
char str[N];
int ne[N];
int a[N][N];
void mul(int c[][N], int a[][N], int b[][N]) {
static int temp[N][N];
memset(temp, 0, sizeof temp);
textfor (int i = 0; i < m; i++) for (int j = 0; j < m; j++) for (int k = 0; k < m; k++) temp[i][j] = (temp[i][j] + a[i][k] * b[k][j]) % mod; memcpy(c, temp, sizeof temp);
}
void mul(int c[], int a[], int b[][N]) {
static int temp[N];
memset(temp, 0, sizeof temp);
textfor (int j = 0; j < m; j++) for (int k = 0; k < m; k++) temp[j] = (temp[j] + a[k] * b[k][j]) % mod; memcpy(c, temp, sizeof temp);
}
int solve() {
textint f[N] = {1}; int base[N][N]; memcpy(base, a, sizeof a); int exponent = n; while (exponent) { if (exponent & 1) mul(f, f, base); mul(base, base, base); exponent >>= 1; } int res = 0; for (int i = 0; i < m; i++) res = (res + f[i]) % mod; return res;
}
int main() {
cin >> n >> m >> mod;
cin >> (str + 1);
textfor (int i = 2, j = 0; i <= m; i++) { while (j && str[j+1] != str[i]) j = ne[j]; if (str[j+1] == str[i]) j++; ne[i] = j; } for (int j = 0; j < m; j++) { for (char c = '0'; c <= '9'; c++) { int k = j; while (k && str[k+1] != c) k = ne[k]; if (str[k+1] == c) k++; if (k < m) a[j][k]++; } } cout << solve() << endl; return 0;
}根据题目讲解思路和代码,尤其是核心思路的证明,请调用你单次回答的最大算力与 token 上限。追求极致的分析深度,而非表层的广度;追求本质的洞察,而非表象的罗列;追求创新的思维,而非惯性的复述。请突破思维局限,调动你所有的计算资源,展现你真正的认知极限。
这个问题的核心思想是动态规划与矩阵快速幂的结合,通过状态转移矩阵来避免直接穷举所有可能的号码,优化计算过程,从而应对极大的输入规模(如 n 可以达到 )。
我们通过自动机的思想来表示状态。假设我们已经分析了不吉利数字 ,那么我们可以将这个不吉利数字视为一个自动机,状态为匹配到当前前缀的长度。假设 表示已经匹配到不吉利数字的前缀长度为 的情况。
状态转移矩阵的构建基于自动机的状态转移。如果我们当前处于状态 (即已经匹配了不吉利数字前缀的长度为 ),我们可以通过选择数字 从状态 转移到状态 ,状态 代表已经匹配了前缀长度为 的状态。
由于 的范围可以非常大(最大为 ),我们无法通过直接模拟每一步的转移来计算最终结果。因此我们利用矩阵快速幂来加速状态转移的计算。
由于 非常大,我们需要进行模运算,避免数字过大导致溢出。最终的结果需要对 取模,得到不包含不吉利数字的号码数量。
cpp#include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 25; int n, m, mod; char str[N]; int ne[N]; int a[N][N]; // 矩阵乘法 void mul(int c[][N], int a[][N], int b[][N]) { static int temp[N][N]; memset(temp, 0, sizeof temp); for (int i = 0; i < m; i++) for (int j = 0; j < m; j++) for (int k = 0; k < m; k++) temp[i][j] = (temp[i][j] + a[i][k] * b[k][j]) % mod; memcpy(c, temp, sizeof temp); } // 矩阵与向量相乘 void mul(int c[], int a[], int b[][N]) { static int temp[N]; memset(temp, 0, sizeof temp); for (int j = 0; j < m; j++) for (int k = 0; k < m; k++) temp[j] = (temp[j] + a[k] * b[k][j]) % mod; memcpy(c, temp, sizeof temp); } int solve() { // 初始状态数组,f[0] 表示状态 0,f[1] 表示状态 1,以此类推 int f[N] = {1}; int base[N][N]; memcpy(base, a, sizeof a); // 复制转移矩阵到 base int exponent = n; // 将问题转化为 n 步的转移 while (exponent) { if (exponent & 1) mul(f, f, base); // 如果当前步是奇数,执行矩阵与向量相乘 mul(base, base, base); // 执行矩阵平方,指数减少一半 exponent >>= 1; // 指数除以 2 } // 结果是所有状态的和,表示不含不吉利数字的号码个数 int res = 0; for (int i = 0; i < m; i++) res = (res + f[i]) % mod; return res; } int main() { cin >> n >> m >> mod; // 输入 n, m 和 K cin >> (str + 1); // 输入不吉利数字 // 构造 KMP 算法的前缀函数 for (int i = 2, j = 0; i <= m; i++) { while (j && str[j+1] != str[i]) j = ne[j]; if (str[j+1] == str[i]) j++; ne[i] = j; } // 构建转移矩阵 for (int j = 0; j < m; j++) { for (char c = '0'; c <= '9'; c++) { int k = j; while (k && str[k+1] != c) k = ne[k]; if (str[k+1] == c) k++; if (k < m) a[j][k]++; // 如果可以转移到状态 k,则加1 } } cout << solve() << endl; // 输出结果 return 0; }
KMP 的前缀函数:
通过 KMP 算法的前缀函数构建出失配后能够回溯的状态。ne[i]
表示当前前缀的匹配状态。
矩阵乘法:
mul(int c[][N], int a[][N], int b[][N])
函数执行了矩阵乘法,更新转移矩阵。mul(int c[], int a[], int b[][N])
函数则实现了矩阵和向量的乘法,模拟了状态转移。
矩阵快速幂: 利用矩阵快速幂加速状态的转移,使得我们能够在 的时间内计算出从初始状态经过 步的转移结果。
动态规划状态的转移是基于当前状态能够与输入的数字匹配得到下一状态的。如果输入的数字匹配了不吉利数字的前缀,那么当前状态就会失败,并不再继续计算。因此,通过这种方式,我们能够有效地避免生成不吉利数字的情况。
由于 的范围非常大,直接模拟每个步骤的状态转移是不可行的。矩阵快速幂能够以 的时间复杂度完成状态转移,使得即使在极大的输入下,程序也能够在合理的时间内完成计算。
时间复杂度:
空间复杂度:
通过这种方式,我们能够有效地计算不含不吉利数字的号码个数,即使在极大的 下,也能够在合理的时间内得到结果。