题意:给定一个[l, r]区间,求其中该数能被其自身每一位数整除的数的个数。
思路:
显然是数位DP。能整除其自身每一位,即能整除其自身每一位数的最小公倍数。lcm(2,...,9)== 2520。对于一个n位数,我们从第一位开始,扫过n位数字以后判断是否满足条件。dp过程中构成的一个n位数字组成的数,最后只需判断其对这n位数的lcm取模是否为0。显然若为0,则返回1;否则为0。
那么我们就可以存三维的dp数组来进行转移。dp[now][lcm][mod],表示前now位数字,其最小公倍数为lcm,对2520取模为mod的dp值。为什么是对2520取模呢?因为对前n位数字的lcm最大只能为2520,所以dp过程中我们只对2520取模,最后判断的时候才对前n位数字的lcm取模。这样得到转移方程:dp[now][lcm][mod] = sigma(dp[now-1][_lcm][_mod])。其中_lcm和_mod为往后更新一位时得到的新的lcm和mod值。注意这里lcm*mod*now < 20 * 2520 * 2520,显然要爆内存。不过打一下表可以发现2~9的最小公倍数组合最多不超过50种,因此可以哈希一下,这样dp数组就可以只开到dp[20][50][2600]。
由于我们对每一位求的不一定是0~9的所有值。因此dp过程中要判断该值是否到达所求数的临界值。用flag标记,然后每一位进行dp时用“||”更新flag。这样如果前面的now-1位没有达到的话,那么第now位是可以取0~9任意值的。否则的话最大只能取到bit[now]。即对应原数的那一位。
最后计算work(r) - work(l-1)即为答案。
#include <algorithm>
#include <iostream>
#include <sstream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <string>
#include <queue>
#include <stack>
#include <cmath>
#include <set>
#include <map>
using namespace std;
typedef long long LL;
#define mem(a, n) memset(a, n, sizeof(a))
#define ALL(v) v.begin(), v.end()
#define si(a) scanf("%d", &a)
#define sii(a, b) scanf("%d%d", &a, &b)
#define siii(a, b, c) scanf("%d%d%d", &a, &b, &c)
#define pb push_back
#define eps 1e-8
const int inf = 0x3f3f3f3f, N = 1e3 + 5, MOD = 1e9 + 7;
int T, cas = 0;
int n, m;
LL dp[20][50][2600], l, r;
int bit[20], hs[2600];
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
LL dfs(int now, int lcm, int mod, bool flag) {
LL& ret = dp[now][hs[lcm]][mod];
if(now == 0) return mod % lcm == 0;
if(flag && ret != -1) return ret;
LL ans = 0;
int x = flag ? 9 : bit[now];
for(int i = 0; i <= x; i ++) {
int _lcm = !i ? lcm : (lcm * i / gcd(lcm, i)),
_mod = (mod * 10 + i) % 2520;
ans += dfs(now - 1, _lcm, _mod, flag || i < x);
}
if(flag) ret = ans;
return ans;
}
LL work(LL x) {
int len = 0;
while(x) {
bit[++ len] = x % 10;
x /= 10;
}
return dfs(len, 1, 0, 0);
}
void init() {
int idx = 1;
for(int i = 1; i <= 2520; i ++) {
if(2520 % i == 0) hs[i] = idx ++;
}
mem(dp, -1);
}
int main(){
#ifdef LOCAL
freopen("/Users/apple/input.txt", "r", stdin);
// freopen("/Users/apple/out.txt", "w", stdout);
#endif
init();
si(T);
while(T --) {
scanf("%lld%lld", &l, &r);
printf("%lld\n", work(r) - work(l - 1));
}
return 0;
}