洛谷P10262 亲朋数-普及/提高-

题目描述

给定一串长度为 L L L、由数字 0 ∼ 9 0\sim 9 09 组成的数字串 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 2p128
输入的第二行,包含一个长为 L L L 的数字串 S S S。约定 1 ≤ L ≤ 10 6 1 \leq L \leq 10^6 1L106

输出格式

输出一行一个整数表示答案。

输入输出样例 #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,(k10+di)modp=dpi1,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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智趣代码实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值