GT考试不吉利数字分析

Erstellt am: 5. Februar 2025

Beantwortet mit O1 von Chat01

Frage

阿申准备报名参加 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);

text
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);

text
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() {

text
int 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);

text
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]++; } } cout << solve() << endl; return 0;

}根据题目讲解思路和代码,尤其是核心思路的证明,请调用你单次回答的最大算力与 token 上限。追求极致的分析深度,而非表层的广度;追求本质的洞察,而非表象的罗列;追求创新的思维,而非惯性的复述。请突破思维局限,调动你所有的计算资源,展现你真正的认知极限。

Gedanke

检查准考证号

正在检查阿申的准考证号,确保没有不吉利的数字。发现这些数字可以影响他考试的好运气。

确保号码的幸运

阿申希望他的准考证号没有不吉利的数字。这让我想到,可能需要过滤掉包含这些数字的号码,确保他能参见幸运的号码。

确保合规性

阿申让我确认他的准考证号不包含不吉利数字。这样做是为了避免这些数字可能带来的负面影响。

确保幸运号码

我正在帮助阿申确认他的准考证号中不包含任何不吉利的数字,确保号码是吉利的。

排除不吉利数字

阿申即将报名参加 GT 考试,他希望准考证号中没有不吉利的数字。为了确保号码的吉祥性,我们正在排查所有不吉利数字。

确认号码幸运

阿申即将参加 GT 考试,准考证号需避免出现任何不吉利数字,以确保号码带来好运。他希望号码能尽可能吉祥。

已推理,持续 10 秒

Antwort

这个问题的核心思想是动态规划与矩阵快速幂的结合,通过状态转移矩阵来避免直接穷举所有可能的号码,优化计算过程,从而应对极大的输入规模(如 n 可以达到 10910^9)。

核心思路解析

1. 状态表示与转移

我们通过自动机的思想来表示状态。假设我们已经分析了不吉利数字 A=A1A2AmA = A_1A_2\ldots A_m,那么我们可以将这个不吉利数字视为一个自动机,状态为匹配到当前前缀的长度。假设 dp[i]dp[i] 表示已经匹配到不吉利数字的前缀长度为 ii 的情况。

  • 初始状态 dp[0]=1dp[0] = 1,即没有匹配到任何前缀的情况。
  • 对于每一个数字 XX 从 0 到 9,我们尝试更新每个状态 dp[i]dp[i]。如果当前状态是 ii,我们可以通过某个数字转移到新的状态 ii',其中 ii' 可能是匹配到不吉利数字 AA 的某个前缀,或者停留在当前状态。

2. 构建状态转移矩阵

状态转移矩阵的构建基于自动机的状态转移。如果我们当前处于状态 jj(即已经匹配了不吉利数字前缀的长度为 jj),我们可以通过选择数字 cc 从状态 jj 转移到状态 kk,状态 kk 代表已经匹配了前缀长度为 kk 的状态。

  • 如果当前状态 jj 和数字 cc 可以继续匹配到不吉利数字的前缀,那么状态 kk 会更新为 j+1j+1
  • 如果匹配失败,那么状态就会回退到某个之前的状态,直到我们找到一个可以继续匹配的状态。

3. 矩阵快速幂的应用

由于 nn 的范围可以非常大(最大为 10910^9),我们无法通过直接模拟每一步的转移来计算最终结果。因此我们利用矩阵快速幂来加速状态转移的计算。

  • 将状态转移矩阵 AA 定义为大小 m×mm \times m 的矩阵,其中每个元素 A[i][j]A[i][j] 表示从状态 ii 到状态 jj 的转移概率。
  • 通过矩阵快速幂,我们可以在 O(logn)O(\log n) 的时间内计算出从初始状态出发经过 nn 步后的最终状态。

4. 最优化与计算模 K

由于 nn 非常大,我们需要进行模运算,避免数字过大导致溢出。最终的结果需要对 KK 取模,得到不包含不吉利数字的号码数量。

详细解析代码

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; }

代码分析

  1. KMP 的前缀函数: 通过 KMP 算法的前缀函数构建出失配后能够回溯的状态。ne[i] 表示当前前缀的匹配状态。

  2. 矩阵乘法mul(int c[][N], int a[][N], int b[][N]) 函数执行了矩阵乘法,更新转移矩阵。mul(int c[], int a[], int b[][N]) 函数则实现了矩阵和向量的乘法,模拟了状态转移。

  3. 矩阵快速幂: 利用矩阵快速幂加速状态的转移,使得我们能够在 O(logn)O(\log n) 的时间内计算出从初始状态经过 nn 步的转移结果。

核心证明

动态规划状态的有效性:

动态规划状态的转移是基于当前状态能够与输入的数字匹配得到下一状态的。如果输入的数字匹配了不吉利数字的前缀,那么当前状态就会失败,并不再继续计算。因此,通过这种方式,我们能够有效地避免生成不吉利数字的情况。

矩阵快速幂的必要性:

由于 nn 的范围非常大,直接模拟每个步骤的状态转移是不可行的。矩阵快速幂能够以 O(logn)O(\log n) 的时间复杂度完成状态转移,使得即使在极大的输入下,程序也能够在合理的时间内完成计算。

复杂度分析

  • 时间复杂度

    • 构建 KMP 前缀函数的时间复杂度为 O(m)O(m)
    • 构建转移矩阵的时间复杂度为 O(m×10)O(m \times 10)(因为每个状态需要遍历所有可能的数字)。
    • 矩阵快速幂的时间复杂度为 O(m3logn)O(m^3 \log n)。 因此,总体时间复杂度为 O(m3logn)O(m^3 \log n)
  • 空间复杂度

    • 主要空间消耗来自转移矩阵、前缀函数数组等,空间复杂度为 O(m2)O(m^2)

通过这种方式,我们能够有效地计算不含不吉利数字的号码个数,即使在极大的 nn 下,也能够在合理的时间内得到结果。

Diesen Q&A teilen