CF 55D Beautiful numbers (数位DP)

题意:给定一个[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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值