|
|
#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;
|
|
|
} |