题目链接:

https://codeforces.com/problemset/problem/55/D

题意:

找到区间范围中的数 \(x\) 的数量,要求 \(x\),能被它每一个非零的数位整除。

思路:

如果一个数能被它所有非零位数整除,那么它一定能被所有非零位数的最小公倍数整除,所以记忆化搜索的过程中要记录第 \(i\) 位之前的所有非零位数的最小公倍数。
接着看余数,因为 1 ~ 9 的最小公倍数为 2520,所以可以将数先对 2520 进行一个取余。这样子就可以定义 \(dp\) 数组 \(dp[i][j][k]\) 表示长度为 \(i\) 的数字,第 \(i\) 位之前所有的数字的最小公倍数为 \(j\),对 2520 取余后余数为 \(k\) 的数字有多少。数组大小为 20 * 2520 * 2520,超内存了。
因为 1 ~ 9 的数字的最小公倍数只有 48 种,所以可以通过离散化的方式让数组大小变成 20 * 50 * 2520。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL dp[20][50][2520], num[20], Hash[2520];
LL dfs(int pos, int limit, int prelcm, int res){
	if (pos == 0) return res % prelcm == 0;
	if (!limit && dp[pos][Hash[prelcm]][res] != -1) return dp[pos][Hash[prelcm]][res];
	LL ans = 0;
	int up = (limit ? num[pos] : 9);
	for (int i = 0; i <= up; i ++ ){
		int t = prelcm;
		if (i) t = lcm(prelcm, i);
		ans += dfs(pos - 1, limit && (i == num[pos]), t, (res * 10 + i) % 2520);
	}
	if (!limit) dp[pos][Hash[prelcm]][res] = ans;
	return ans;
}
LL solve(LL x){
	int len = 0;
	while(x){
		num[++ len] = x % 10;
		x /= 10;
	}
	return dfs(len, 1, 1, 0);
}
void solve(){
	memset(dp, -1, sizeof dp);
	LL a, b;
	cin >> a >> b;
	cout << solve(b) - solve(a - 1) << "\n";
}
void init(){
	int cnt = 0;
	for (int i = 1; i <= 2520; i ++ )
		if (2520 % i == 0)
			Hash[i] = ++ cnt;
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	init();
	LL T;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}
posted on 2022-07-15 18:31  Hamine  阅读(25)  评论(0编辑  收藏  举报