AtCoder-465-E-数位DP

文章内容

Digit Circus

涉及知识点: 数位DP、状态压缩、记忆化搜索
题目描述
请你计算满足以下三个条件中恰好一个的整数 的个数,范围是 ,结果对 998244353 取模。

  • 是 3 的倍数。
  • 的十进制表示中包含数字 3。
  • 的十进制表示中恰好用了三种不同的数字。
    注意,整数的十进制表示不允许有多余的前导 0。
    约束条件
  • 是一个整数。

  • 输入格式
    输入 。
    输出格式
    输出答案。
    代码讲解
    最大能到 ,普通的遍历必超时,只能用数位 DP。我们在按位枚举填数时,需要维护状态来判断题目给的三个条件。
    条件一是判断 3 的倍数,利用特征只需记录当前各数位之和对 3 的余数(rem)。
    条件二和三涉及具体出现了哪些数字,用一个二进制状态掩码(mask)来存数字集合最方便。用 (mask & (1 << 3)) != 0 可以查集合里有没有 3,用 __builtin_popcount(mask) == 3 能直接拿到集合里不同数字的总数。
    逻辑核心是递归函数 dfs(pos, rem, mask, isLimit, isHead)。到了递归边界(所有位填完),先把全 0 的情况(isHead 仍为真)排除。接着看三个条件命中了几个,恰好 1 个就返回 1,其余返回 0。
    填数转移时,前导 0 是不计入数字集合的。处于前导 0 阶段且填入 0 时,mask 和 rem 都不更新。
    你提供的代码里有一个经典的记忆化漏洞。你在读取 memo 时加上了 !isLimit && !isHead 的限制,但在底部写入 memo 时直接存了进去。受限状态(比如最高位只能枚举到 4)和前导零状态跑出来的结果,不是通用的排列组合。
    正解代码
cpp
#include <bits/stdc++.h>using namespace std;#define int long long#define endl "\n"const int MOD = 998244353;void solve() {    string n;    cin >> n;    int memo[n.size()+1][3][1024];    memset(memo, -1, sizeof(memo));    auto dfs = [&](auto&& dfs, int pos, int rem, int mask, bool isLimit, bool isHead)->int {        int r = 0;        if (pos == n.size()) {            if (isHead) return 0;            int a = rem == 0;            int b = __builtin_popcount(mask) == 3;            int c = (mask & (1 << 3)) != 0;            if (a + b + c == 1) return 1;            return 0;        }        if (!isLimit && !isHead && memo[pos][rem][mask] != -1) {            return memo[pos][rem][mask];        }        int up = (isLimit) ? n[pos]-'0' : 9;        for (int i = 0; i <= up; ++i) {            int maskNew = mask, remNew = rem;            if (!(isHead && i==0)) {                maskNew = mask | (1 << i);                remNew = (rem + i) % 3;            }            r = r + dfs(dfs, pos+1, remNew, maskNew, isLimit & (i==up), isHead && (i == 0)) % MOD;            r %= MOD;        }        // 修复:写入记忆化时必须确保状态不受限且无前导零        if (!isLimit && !isHead) {            memo[pos][rem][mask] = r;        }        return r;    };    cout << dfs(dfs, 0, 0, 0, 1, 1);}signed main() {    ios::sync_with_stdio(false);    cin.tie(0);    int T = 1;    while (T--) {        solve();    }    return 0;}

觉得不错?给作者点个赞吧

分类:默认

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

AtCoder-465-E-数位DP · Ensan 的博客