数位dp。(要考虑前导0,类似还有POJ 3252)
定义一种数,要求在其十进制数位上,较高位都>=
较低位,且较高位%
较低位都是0
(较高位都能被较低位整除)。问你[L,R]
内有多少个这种数。
需要注意几点。
- dp第二维记录第
i+1
位数字,整除关系可以传递。中途判断。
默认之前位都已满足要求,所以只要当前位能整除第i+1
位,就可整除前面任意一位。 - 满足要求的数中,只有一个数可以含有
0
,那就是0
本身。因为除数不能为0
。 - 对于
lead
模式下的第二维状态,本来我想用j=10
来表示这种“这个数还没开始”的意思,因为这样不会冲突。
但由于第二点可得知,j=0
这个状态其实不会“正常”存在,那就让lead
来占这个坑也无妨,也符合本意。 - 其实。。我试了一下之后发现,上述第三点是我多虑了,初始调用dfs时无论
status
是几,都能AC。(因为lead
模式下不会用dp值,所以不用担心冲突?) - 再总结一下吧。。。
(1)其实lead
模式就是一条生成数字0
的链,在这条链上状态应该都不变。
(2)lead
模式可以转为!lead
模式,反之不成立。 - 对这道题而言,在
!lead
模式下对转移的数字有限制。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <string>
#include <queue>
using namespace std;
int T, L, R;
int dp[10][10]; // j表示上一位
int num[10];
int dfs(int pos, int status, bool lead, bool limit)
{
if (pos == -1) return 1;
if ((!limit) && (!lead) && (dp[pos][status] != -1)) return dp[pos][status];
int cnt = 0;
int up = (limit ? num[pos] : 9);
for (int i = 0; i <= up; i++) // 除了前导0和数字0本身,其他都不能有0。因为不能 mod 0
{
if (!lead)
{
if (i == 0) continue; // 绝对不能除以0,所以这句提前
if ((status % i) != 0) continue;
if (i > status) break; // 之后的更大,更不可能
}
if (lead && (i == 0)) cnt += dfs(pos - 1, status, true, limit && (i == up)); // 此时status一定等于0
else cnt += dfs(pos - 1, i, false, limit && (i == up));
}
if ((!limit) && (!lead)) dp[pos][status] = cnt;
return cnt;
}
int solve(int x) // 验证 x=0,也要验证当 x!=0 时 solve(x) 是否正确包含了0
{
int pos = 0;
for (; x;)
{
num[pos++] = x % 10;
x /= 10;
}
return dfs(pos - 1, 0, true, true); // lead=true可推得status=0,而且在这道题中,其反向也成立。
}
int main()
{
scanf("%d", &T);
memset(dp, -1, sizeof dp);
for (; T--;)
{
scanf("%d%d", &L, &R);
printf("%d\n", solve(R) - solve(L - 1));
}
return 0;
}