AT_tdpc_number 数 解题报告

题目大意

\([1, N]\) 中有多少个数在十进制表示下数码和是 \(D\) 的倍数。

数据范围:\(1\le N\le 10^{10000},1\le D\le 100\)

思路

很明显的数位 dp。

这里采用了记忆化搜索来实现数位 dp。

记忆化搜索实现比较板子,不光写起来比较简单,而且容易扩展,所以建议大家都学习一下。

首先把上界 \(N\) 的每一位抠出来,然后进行填数,个人喜欢从最高位开始填。

加上记忆化,设 \(f(pos, r)\) 表示在没有顶上界和前导零的情况下,当前填到了第 \(pos\) 位,余数为 \(r\) 的数的个数。

然后在搜索过程中记一下当前数位和 \(\bmod p\) 等于多少,再简单转移一下即可,详细注释在代码中。

注意到状态数为 \(D\cdot\lg N\),每次转移时最多枚举 \(10\) 个可填的数,所以时间复杂度为 \(O(D\cdot \lg N)\),可以通过此题。

注意!由于最后要 \(-1\),所以为防止减为负数要先加上模数再取模。

\(\texttt{Code:}\)

#include <cmath>
#include <vector>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 10010, M = 110, mod = 1e9 + 7;
typedef long long ll;
char s[N];
int len, d;
int f[N][M];
vector<int> num;

//记忆化搜索
//pos 记录当前填到了哪一位,r 记录填完 pos + 1 到 len - 1 位后数位和模 d 的值
//limit 记录当前有没有顶上界,zero 记录当前是不是前导零
ll dfs(int pos, int r, bool limit, bool zero) {
    //边界,若填完了就检查一下是否符合条件
    if(pos < 0) return (r == 0);
    //若不顶上界且没有前导零就记忆化,因为顶上界和前导零是特殊情况,满足条件的数可能和普通情况不同
    if(!limit && !zero && f[pos][r] != -1) return f[pos][r];
    //看一下当前这位需不需要顶上界,若前面填的数都是贴着上界的,这一位最多只能填到 num[pos],否则不受限
    int mx = (limit ? num[pos] : 9);
    ll res = 0;
    //枚举第 pos 位填什么
    for(int i = 0; i <= mx; i++)
        //去填下一位,更新当前余数,看下一位是不是顶上界,看下一位是不是前导零
        res = (res + dfs(pos - 1, (r + i) % d, limit && (i == num[pos]), zero && (!i))) % mod;
    //若不顶上界且没有前导零就记忆化
    if(!limit && !zero) f[pos][r] = res;
    return res;
}

ll calc(char str[]) {
    //把每一位抠出来,注意是倒过来的
    for(int i = len - 1; i >= 0; i--)
        num.push_back(s[i] - '0');
    memset(f, -1, sizeof f);
    return dfs(len - 1, 0, 1, 1);
}

int main() {
    scanf("%s%d", s, &d);
    len = strlen(s);
    //因为我们搜索考虑了 0,最后要把一减去
    printf("%lld", (calc(s) + mod - 1) % mod);
    return 0;
}
posted @ 2024-08-28 21:33  Brilliant11001  阅读(1)  评论(0编辑  收藏  举报