题目背景
这道题来自洛谷 GESP 样题六级,考察的是对大数字串进行整除判断的能力。给定一个长度为 L 的数字串 S 和一个正整数 p,我们需要统计其中有多少个连续子串对应的数值是 p 的倍数。
问题分析
直接枚举所有子串并计算数值显然会溢出且超时。关键在于利用模运算的性质:
- 一个长数字串可以看作是由前缀不断左移一位(乘 10)加上当前位组成的。
- 如果已知以位置 i-1 结尾的所有子串对 p 取模的余数分布,那么扩展到位置 i 时,只需将之前的余数乘以 10 再加上当前数字,再对 p 取模即可得到新的余数。
- 我们只需要记录每个余数出现的次数,当余数为 0 时,说明找到了一个符合条件的子串。
这种思路本质上是一种动态规划,状态定义为 dp[remainder],表示以当前位置结尾、余数为 remainder 的子串数量。
输入输出规范
输入: 第一行包含一个正整数 p(2 ≤ p ≤ 128)。 第二行包含长为 L 的数字串 S(1 ≤ L ≤ 10^6)。
输出: 一行一个整数,表示满足条件的子串总数。
代码实现
下面给出完整的 C++ 实现。注意处理大数组和类型转换,避免中间过程溢出。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll; // 使用 long long 防止计数溢出
const int MAX_P = 150;
int p, f[MAX_P], g[MAX_P];
ll ans = 0;
string s;
int main() {
// 优化 IO 速度
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> p >> s;
int len = s.size();
// 为了方便处理,在字符串前补一个空格,使下标从 1 开始
s = " " + s;
for (int i = 1; i <= len; i++) {
// 清空当前轮次的计数数组
memset(g, 0, sizeof(g));
( j = ; j < p; j++) {
(f[j] == ) ;
t = (j * + (s[i] - )) % p;
g[t] += f[j];
}
single_rem = (s[i] - ) % p;
g[single_rem]++;
ans += g[];
(f, g, (g));
}
cout << ans << endl;
;
}


