Files
python/TangDou/AcWing/DP/ShuWei/ShuWeiDpTemplate.cpp
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

94 lines
4.7 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <bits/stdc++.h>
using namespace std;
const int N = 32; // 输入的数据范围2^31-1,也就是整数上界。2进制是最小的进制,32也够了
int a[N], al;
int f[N][N];
/*
f[i][j]:
i:所在数位第几位,比如469,出发时,就是站在第3位,即4这个位置出发,一般从高位到低位进行,起始值是最高位
j:st的意思,配合位置i,描述一下当前的情况
举个栗子: f[3][4]:走到了位3这个数位,前面已经取得了4个是1的数位,此时,后续的符合条件条件的数有f[3][4]个
第二维还是因题而异的,需要针对不同题目进行思考分析。
*/
/*
功能:计算以当前状态出发,会收集到多少个符合条件的数
参数:
u :当前是第几位,比如 421,开始的时候,就是第3位,数值是4
st :记录状态传递变量,比如数字1出现的次数
lead :需不需要考虑前导零
op :是否贴上界
*/
int dfs(int u, int st, bool lead, bool op) {
// 递归边界,既然是按位枚举,最低位是1,那么u==0说明这个数我枚举完了
if (u == 0) return 1; /*这里一般返回1,表示你枚举的这个数是合法的,
那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到u位,
一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
// 第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
// 不贴上界,不需要考虑前导零,以前计算过,这样的东西才能拿来即用
if (!op && !lead && ~f[u][st]) return f[u][st];
/*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
int up = op ? a[u] : 9; // 根据limit判断枚举的上界up;这个的例子前面用213讲过了
int ans = 0;
// 开始计数
for (int i = 0; i <= up; i++) { // 枚举,把不同情况的个数加到ans就可以了
// 这里可以加一些减枝之类的代码
/*
代码细节:
==的运算优先级 高于 && ,所以先判断 i == a[pos]是第一步,与 op 相 && 是第二步
pos-1:这玩意是从高位到低位的,由大到小枚举,所以和平常的dfs有点区别,是pos-1
逐句解读:
lead && i == 0:
如果前面考虑前导0,现在i ==0 ,则后面的数字枚举,仍然要考虑前导零。
如果前面考虑前导0,现在i>0,则后面的数字枚举,不需要考虑前导零。
如果前面不考虑前导0,后面就不用考虑这个前导零的问题。
op && i == a[u] :
如果原来op=true,即贴上界,而且,当前位枚举的数字i也和原数字位一致,那么后面的数字枚举,必然也继续贴上界
如果原来op=false,就是原来就不贴上界,越往后也不会贴上界了。
*/
st = st + i; // 这句话是灵活的,因题而异,一般是描述传递状态的变更,比如选择当前数位的数字1,就多了一个1,需要+1等等
ans += dfs(u - 1, st, lead && i == 0, op && i == a[u]);
/*这里还算比较灵活,不过做几个题就觉得这里也是套路了
大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
去计算不同情况下的个数,还有要根据st变量来保证i的合法性,比如题目
要求数位上不能有62连续出现,那么就是st就是要保存前一位pre,然后分类,
前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法
*/
}
// 计算完,记录状态
if (!op && !lead) f[u][st] = ans;
/*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
return ans;
}
// 因为用前缀和思想,所以要计算r和l-1两次,封成一个calc函数。
int calc(int x) {
al = 0; // 注意清零,al清零即可,a不用memset清零
// 把数位都分解出来
while (x) a[++al] = x % 10, x /= 10; // 个人喜欢编号为[1,al]
return dfs(al, 0, true, true); // 从最高位开始枚举,刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}
int main() {
int l, r;
while (cin >> l >> r) {
// 初始化dp数组为-1
memset(f, -1, sizeof f);
printf("%lld\n", calc(r) - calc(l - 1));
}
return 0;
}