1 min read 211 words Updated Apr 25, 2026 Created May 03, 2026

数位 DP 入门

最近一直在刷数位 DP 的题,于是写这篇博客总结一下。

本质

数位 DP 与其说是 DP 不如说是一种记忆化搜索的思路。

看到求 $[L, R]$ 间满足某个性质的数有多少个,并且 $L, R$ 很大的时候可能就需要用到这个方法。

由于同时处理上下界的搜索会非常麻烦,所以需要小小差分一下,也就是利用 $[0/1, R]$ 满足性质的数减去 $[0/1, L)$ 满足性质的数。

是否需要 $0/1$ 依据题意而言。

思想

搜索是从高位开始向低位填数。所填之数需要存在一个性质或者状态与填数的方案无关,也就是无论填什么数,只要满足这个性质,后面都可以填相同的数

入门题

[USACO06NOV] Round Numbers S - 洛谷

如果一个正整数的二进制表示中,$0$ 的数目不小于 $1$ 的数目,那么它就被称为「圆数」。

例如,$9$ 的二进制表示为 $1001$,其中有 $2$$0$$2$$1$。因此,$9$ 是一个「圆数」。

请你计算,区间 $[l,r]$ 中有多少个「圆数」。

分析

需要存在的性质与 $0, 1$ 的个数只差有关,所以状态可以设为个数之差。

可以发现只要差一样,那么后面可以填的数的个数相同。

这满足记忆化的要求,所以可以上搜索。

但是我们会发现前导零会对答案造成影响,所以需要加上一个参数来表示是否有前导零的存在,也就是参数中的 zero

// 由于 d 可能为负数,所以需要加上一个 OFFSET 强制转化为正数
int dfs(int p, int d, bool zero, bool up) {
    if (p == 0) return d >= 0;
    if (!up && !zero && ~f[p][d + OFFSET]) { 
        return f[p][d + OFFSET];
    }

    int res = 0;
    for (int i = 0, ie = up ? s[p] : 1; i <= ie; ++i) {
        res += dfs(p - 1, i ? d - 1 : (zero ? d : d + 1), zero && (i == 0), up && (i == ie));
    }

    if (!up && !zero) f[p][d + OFFSET] = res;
    return res;
}

到这里,只是思想上,实现上还有很多问题

如何保证是在上界内?

首先我们将目标数按照需要的进制进行拆分:

int solve(int M) {
    memset(f, -1, sizeof(f));
    int len = 0;
    while (M) {
        s[++len] = M & 1;
        M >>= 1;
    } return dfs(len, 0, true, true);
}

然后在 dfs 时利用 up 判断是否抵着上界在搜索即可。