[CodeForces-55D]Beautiful Numbers 数位dp+离散化 可真奇妙

本文介绍了一种解决特定数学问题的算法——数位动态规划(DP)。通过分析美丽数(其所有非零组成数字都能整除自身)的特性,文章详细解释了如何利用数位DP算法,在给定范围内高效计算美丽数的数量。关键在于离散化最小公倍数概念,优化存储需求,并提供了一个AC代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这有一堆美丽 数

Beautiful number

Volodya is an odd boy and his taste is strange as well. It seems to him that a positive integer number is beautiful if and only if it is divisible by each of its nonzero digits. We will not argue with this and just count the quantity of beautiful numbers in given ranges.

Input

The first line of the input contains the number of cases t (1 ≤ t ≤ 10). Each of the next t lines contains two natural numbers li and ri (1 ≤ li ≤ ri ≤ 9 ·1018).

Please, do not use %lld specificator to read or write 64-bit integers in C++. It is preffered to use cin (also you may use %I64d).

Output

Output should contain t numbers — answers to the queries, one number per line — quantities of beautiful numbers in given intervals (from li to ri, inclusively).

Examples

Input

1
1 9

Output

9

Input

1
12 15

Output

2

有一种美丽的数,他的特征是他的每一个非零组成数字都能够整除他本身,给你一个范围,让你求出这个范围内有多少个这样的beautiful number ?

这是一个多组样例,而且给的数的范围及其之大,~9e18,这谁能降得住啊,肯定又有一些奇奇怪怪的技巧,首先这是一个数位dp题,这就将时间复杂度降了下来,但是你要开一个及其之大的dp数组来存结果,这又是一个谜

咱们先普及一下这道题会用到的背景知识,若一个数能整除它的所有的非零组成数字,那么相当于它能整除所有非零组成数字的的最小公倍数。

其实 1~9的最小公倍数是2520,也就是说所有的组成数字的最小公倍数的最大值不会大于2520.

所以把这个数%2520,得到的数字如果还可以整除数字的lcm的话,那么这个数还是美丽数,,至于证明过程还真不会,但是举两个例子就可以理解的

我们可以设一个dp数组,dp[25][2525][2525],分别表示pos当前在第几位,pmod前面的数字对2520的取模,plcm前面的数的lcm

但是这么大的数组根本没法开啊,可定还需要进一步优化

身边的大佬悄咪咪地tell me 其实枚举1~9的有效最小公倍数只有48个。。。大佬就是大佬,厉害,Orz

这样我们就可以使用离散化的思想了,即记录一下可以被2520整除的数字,留下他的id,这样碰见的时候直接对应一下就OJBK喽

剩下的就是板子了,需要注意的只有就是dfs的内部写法还有要使用long long 不然会WA的很惨

下面是我的AC代码:

//ll dfs(ll pos,ll pmod,ll plcm,ll limit){//pos,前面的数对2520的mod,前面数的lcm,limit  
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const ll inf=0x3f3f3f3f;
const ll lcmm=2520;
ll dp[25][lcmm+10][50];//pos,pmod,id[plcm]
ll id[lcmm+10];//记录plcm的id 
ll a[25];

ll gcd(ll a,ll b){
	if(b==0)return a;
	return gcd(b,a%b);
}
ll lcm(ll a,ll b){
	return a/gcd(a,b)*b;
}

void init(){
	ll cnt=0;
	for(ll i=1;i<=lcmm;i++)
		if(lcmm%i==0)
			id[i]=cnt++;//离散化  记录id 
	mem(dp,-1);
}

ll dfs(ll pos,ll pmod,ll plcm,ll limit){//pos,前面的数对2520的mod,前面数的lcm,limit 
	if(pos<0)return pmod%plcm==0;
	if(!limit&&~dp[pos][pmod][id[plcm]])
		return dp[pos][pmod][id[plcm]];
	ll res=0;
	ll up=limit?a[pos]:9;
	for(ll i=0;i<=up;i++){
		ll nmod=(pmod*10+i)%lcmm;//前面累积的mod 
		ll nlcm=plcm;//注意非零的时候 
		if(i) nlcm=lcm(plcm,i);//新的 lcm 
		
		res+=dfs(pos-1,nmod,nlcm,i==a[pos]&&limit);
	}
	if(!limit) dp[pos][pmod][id[plcm]]=res;
	return res;
}

ll solve(ll x){
	ll cnt=0;
	while(x){
		a[cnt++]=x%10;
		x/=10;
	}
	return dfs(cnt-1,0,1,1);
}

int main()
{
	ll t;
	cin>>t;
	init();//又忘了写了。。。
	while(t--){
		ll x,y;
		cin>>x>>y;
		ll res=solve(y)-solve(x-1);	
		cout<<res<<endl;
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值