题目描述
给定一串长度为 L L L、由数字 0 ∼ 9 0\sim 9 0∼9 组成的数字串 S S S。容易知道,它的连续子串儿共有 L ( L + 1 ) 2 \frac{L(L + 1)}2 2L(L+1) 个。如果某个子串对应的数(允许有前导零)是 p p p 的倍数,则称该子串为数字串 S S S 对于 p p p 的亲朋数。
例如,数字串 S S S 为“ 12342 12342 12342 ”、 p p p 为 2 2 2,则在 15 15 15 个连续子串中,亲朋数有“ 12 12 12 ”、“ 1234 1234 1234 ”、“ 12342 12342 12342 ”、“ 2 2 2 ”、“ 234 234 234 ”、“ 2342 2342 2342 ”、“ 34 34 34 ”、“ 342 342 342 ”、“ 4 4 4 ”、“ 42 42 42 ”、“ 2 2 2 ”等共 11 11 11 个。注意其中“ 2 2 2 ”出现了 2 2 2 次,但由于其在 S S S 中的位置不同,记为不同的亲朋数。
现在,告诉你数字串 S S S 和正整数 p p p ,你能计算出有多少个亲朋数吗?
输入格式
输入的第一行,包含一个正整数
p
p
p。约定
2
≤
p
≤
128
2 \leq p \leq 128
2≤p≤128。
输入的第二行,包含一个长为
L
L
L 的数字串
S
S
S。约定
1
≤
L
≤
10
6
1 \leq L \leq 10^6
1≤L≤106。
输出格式
输出一行一个整数表示答案。
输入输出样例 #1
输入 #1
2
102
输出 #1
5
输入输出样例 #2
输入 #2
2
12342
输出 #2
11
说明/提示
样例 1 解释
5 5 5 个亲朋数,分别 10 10 10、 102 102 102、 0 0 0、 02 02 02、 2 2 2。
解析
本题 L 长度较大,如果直接统计子串肯定超时,所以需要更高效的方法,考虑到需要统计每个子串是否能被 p 整除。如果用
d
p
i
,
j
dp_{i,j}
dpi,j表示:以第 i 个数字结尾,余数为 j 的子串个数,则该子串只有两种可能,一种是第 i 个字符单独作为子串,第 i 个字符与 以第 i - 1 个字符为结尾的子串拼接。所以有递推公式
d
p
i
,
(
k
∗
10
+
d
i
)
m
o
d
p
=
d
p
i
−
1
,
k
dp_{i,(k * 10 + d_i) mod p} = dp_{i - 1,k}
dpi,(k∗10+di)modp=dpi−1,k
其中 d_i 表示第 i 个数字,然后加上第 i 个字符单独作为子串的情况即可。
代码
/*
* dp[i][j] = x: 第 i 个位置为子串结尾余数为j的子串有x个
*/
int dp[N][128];
int main() {
int p;
string s;
cin >> p >> s;
dp[0][(s[0] - '0') % p] = 1;
for (int i = 1; i < s.size(); i++) {
int x = s[i] - '0';
for (int k = 0; k < p; k++) {
dp[i][(k * 10 + x) % p] += dp[i - 1][k];
}
dp[i][x % p]++;
}
long long cnt = 0;
for (int i = 0; i < s.size(); i++) {
cnt += dp[i][0];
}
cout << cnt;
return 0;
}
由于此处的 d p i dp_i dpi 只对递推后面一个有用,所以可以用更省空间的写法
int dp[2][128];
int main() {
int p;
string s;
cin >> p >> s;
long long cnt = (s[0] - '0') % p == 0;
dp[0][(s[0] - '0') % p] = 1;
for (int i = 1; i < s.size(); i++) {
int x = s[i] - '0';
for (int k = 0; k < p; k++) {
dp[i & 1][(k * 10 + x) % p] += dp[(i - 1) & 1][k];
dp[(i - 1) & 1][k] = 0;
}
dp[i & 1][x % p]++;
cnt += dp[i & 1][0];
}
cout << cnt;
return 0;
}