You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

94 lines
4.7 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#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这个位置出发,一般从高位到低位进行,起始值是最高位
jst的意思配合位置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;
}