UVa 12040 Again Lucky Numbers
题目描述
给定一个正整数 NNN 和一个正整数 MMM(长度可达 100100100 位,以字符串形式给出,无前导零),数字 MMM 被视为不吉利的数字。一个 NNN 位数(首位不能为 000,但当 N=1N = 1N=1 时允许该位为 000)如果其十进制表示中不包含子串 MMM,则称为幸运数字。
请计算满足条件的幸运数字的个数。结果可能很大,对 100000071000000710000007 取模。
输入格式
第一行包含一个整数 TTT(T≤1000T \le 1000T≤1000),表示测试用例数。
接下来 TTT 行,每行两个正整数 NNN 和 MMM,其中 NNN 是一个整数(1≤N≤1001 \le N \le 1001≤N≤100),MMM 是一个可能长达 100100100 位的数字字符串。
输出格式
对于每个测试用例,输出一行一个整数,表示幸运数字的个数对 100000071000000710000007 取模的结果。
样例
输入
3
1 3
2 13
2 1
输出
9
89
72
样例解释
- N=1N = 1N=1,M=3M = 3M=3:一位数字有 0∼90 \sim 90∼9,其中不含
3的有 999 个(0,1,2,4,5,6,7,8,90,1,2,4,5,6,7,8,90,1,2,4,5,6,7,8,9)。 - N=2N = 2N=2,M=13M = 13M=13:所有两位数为 10∼9910 \sim 9910∼99,共 909090 个,其中包含
13的只有 131313 这一个,故答案为 898989。 - N=2N = 2N=2,M=1M = 1M=1:所有两位数中,十位不能为 000,且不含
1。十位可取 2∼92 \sim 92∼9(888 种),个位可取 0,2∼90,2 \sim 90,2∼9(999 种),共 8×9=728 \times 9 = 728×9=72。
题目分析
本题的核心是计数长度为 NNN、首位非零且不包含给定模式串 MMM 的数字串个数。由于 NNN 很小(N≤100N \le 100N≤100),但 MMM 可以很长(100100100 位),因此不能枚举数字串,而是需要利用自动机状态转移进行动态规划。
考虑先放宽限制,允许前导零,计算长度为 LLL 的任意数字串(允许前导零)中不含 MMM 的个数,记为 f(L)f(L)f(L)。那么最终答案可以通过容斥得到:
- 当 N=1N = 1N=1 时,首位为零的数字就是
0,它不含任何正整数 MMM(因为 M≥1M \ge 1M≥1),因此答案就是 f(1)f(1)f(1)。 - 当 N>1N > 1N>1 时,首位为 000 的串有 10N−110^{N-1}10N−1 个,但不含 MMM 的个数等于 f(N−1)f(N-1)f(N−1)(因为 MMM 不以 000 开头,所以首位 000 不会产生匹配影响)。因此实际答案为 f(N)−f(N−1)f(N) - f(N-1)f(N)−f(N−1)。
现在核心问题是计算 f(L)f(L)f(L)。我们可以在每个位置依次填入数字,并动态维护当前已匹配 MMM 的前缀长度。这与字符串匹配中的 KMP\texttt{KMP}KMP 自动机一致:状态表示当前已经匹配到 MMM 的哪个前缀位置(000 到 ∣M∣−1|M|-1∣M∣−1)。当读入一个数字 ddd 后,根据 MMM 的失配函数转移到新状态。若新状态等于 ∣M∣|M|∣M∣,则说明完整地出现了 MMM,该转移非法,否则合法。
由于 NNN 只有 100100100,状态数最多为 ∣M∣≤100|M| \le 100∣M∣≤100,转移数 101010,因此可以直接递推。
解题思路
构建 KMP\texttt{KMP}KMP 自动机
- 对模式串 MMM 计算前缀函数(next\textit{next}next 数组)。
- 对于每个状态 sss(0≤s<∣M∣0 \le s < |M|0≤s<∣M∣)和每个数字 ddd(0∼90 \sim 90∼9),模拟 KMP\texttt{KMP}KMP 匹配过程,得到新状态 s′s's′。如果 s′=∣M∣s' = |M|s′=∣M∣,表示匹配到了完整的 MMM,则这个转移不可用;否则可用。
动态规划计算 f(L)f(L)f(L)
定义 dp[ℓ][s]\textit{dp}[\ell][s]dp[ℓ][s] 表示长度为 ℓ\ellℓ、且当前匹配状态为 sss 的合法数字串个数(允许前导零)。初始 dp[0][0]=1\textit{dp}[0][0] = 1dp[0][0]=1。对于每个 ℓ\ellℓ,枚举所有状态 sss,然后尝试每个数字 ddd,若转移到的 s′s's′ 不是 ∣M∣|M|∣M∣,则进行累加:
dp[ℓ+1][s′]+=dp[ℓ][s] \textit{dp}[\ell+1][s'] \mathrel{+}= \textit{dp}[\ell][s] dp[ℓ+1][s′]+=dp[ℓ][s]
所有运算取模 100000071000000710000007。最终:
f(L)=∑s=0∣M∣−1dp[L][s] f(L) = \sum_{s=0}^{|M|-1} \textit{dp}[L][s] f(L)=s=0∑∣M∣−1dp[L][s]
由于 N≤100N \le 100N≤100,直接递推即可。
答案计算
- 若 N=1N = 1N=1,答案为 f(1)f(1)f(1)。
- 否则,答案为 (f(N)−f(N−1)+MOD) mod MOD(f(N) - f(N-1) + \textit{MOD}) \bmod \textit{MOD}(f(N)−f(N−1)+MOD)modMOD。
复杂度分析
- 对于每个测试用例,构建自动机需 O(∣M∣⋅10)O(|M| \cdot 10)O(∣M∣⋅10),递推需 O(N⋅∣M∣⋅10)O(N \cdot |M| \cdot 10)O(N⋅∣M∣⋅10)。
- 总时间复杂度 O(T⋅(N⋅∣M∣⋅10))O(T \cdot (N \cdot |M| \cdot 10))O(T⋅(N⋅∣M∣⋅10)),在 N,∣M∣≤100N, |M| \le 100N,∣M∣≤100,T≤1000T \le 1000T≤1000 时约为 10810^8108 次运算,可接受。
- 空间复杂度 O(∣M∣)O(|M|)O(∣M∣)(存储转移表和 dp\textit{dp}dp 数组),若一次性构建转移表则为 O(∣M∣⋅10)O(|M| \cdot 10)O(∣M∣⋅10)。
代码实现
// Again Lucky Numbers
// UVa ID: 12040
// Verdict: Accepted
// Submission Date: 2026-06-24
// UVa Run Time: 0.030s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
const int MOD = 10000007;
while (T--) {
int N;
string M;
cin >> N >> M;
int len = (int)M.size();
// 构建 KMP 前缀函数
vector<int> nextArr(len, 0);
for (int i = 1; i < len; ++i) {
int j = nextArr[i - 1];
while (j > 0 && M[i] != M[j]) j = nextArr[j - 1];
if (M[i] == M[j]) ++j;
nextArr[i] = j;
}
// 构建自动机转移表 trans[state][digit] -> 新状态(可能等于 len,表示完全匹配)
vector<vector<int>> trans(len, vector<int>(10, 0));
for (int state = 0; state < len; ++state) {
for (int digit = 0; digit < 10; ++digit) {
char c = char('0' + digit);
int ns = state;
while (ns > 0 && M[ns] != c) ns = nextArr[ns - 1];
if (M[ns] == c) ++ns;
trans[state][digit] = ns; // 可能为 len
}
}
// dp[length][state]:长度 length 的串,匹配状态为 state 的方案数(允许前导零)
vector<vector<int>> dp(N + 1, vector<int>(len, 0));
dp[0][0] = 1;
vector<int> f(N + 1, 0);
f[0] = 1; // 空串
for (int length = 1; length <= N; ++length) {
for (int state = 0; state < len; ++state) {
int cur = dp[length - 1][state];
if (cur == 0) continue;
for (int digit = 0; digit < 10; ++digit) {
int ns = trans[state][digit];
if (ns < len) { // 未完全匹配 M,合法
dp[length][ns] = (dp[length][ns] + cur) % MOD;
}
}
}
int sum = 0;
for (int state = 0; state < len; ++state)
sum = (sum + dp[length][state]) % MOD;
f[length] = sum;
}
int ans;
if (N == 1) ans = f[1];
else ans = (f[N] - f[N - 1] + MOD) % MOD;
cout << ans << '\n';
}
return 0;
}
总结
本题是一道典型的基于 KMP\texttt{KMP}KMP 自动机的计数动态规划问题。关键技巧在于:
- 利用 KMP\texttt{KMP}KMP 的失配指针构建自动机,将“不包含子串”的约束转化为状态转移的合法性判断。
- 采用容斥思想,先计算允许前导零的答案,再减去首位为零的情况,从而得到最终合法的 NNN 位数个数。
- 由于 NNN 和 MMM 的长度都很小,直接二维 dp\texttt{dp}dp 递推即可,无需矩阵快速幂等高级优化。
这种方法同样适用于其他类似的“不包含给定模式串”的数字计数问题,只需将模式串长度和 NNN 的规模适当调整即可。处理大模数时注意取模操作,避免负数。
更多推荐


所有评论(0)