【数位DP】CF 55D Beautiful numbers
题目大意
洛谷链接
给出一个正整数\(n\),若这个数可以被其每一位上的数整除,那么就称其为\(Beautiful\ Number\)。给出一个区间,求该区间中\(Beautiful\ Number\)的个数。
输入格式
第一行是数据组数\(t\)。
每组给出\(l_i\)和\(r_i\),为区间的左端点和右端点。
不要用\(\%lld\)读入长整形。建议使用\(cin\)或\(\%I64d\)。(\(cin\)党的胜利吗orz)
数据范围
\(1\le t \le 10,1\le l_i\le r_i\le 9×10^{18}\)
输出格式
输出\(t\)行,每行给出每组数据的答案。
样例1输入
1
1 9
样例1输出
9
样例2输入
1
12 15
样例2输出
2
思路
前置知识
- 一个数可以同时被几个数整除,那么这个数也可以被这几个数的最小公倍数整除。
- 1、2、3、……、10的最小公倍数为2520。
正解
这道题正解就是数位DP(还是到黑题老姚迫害学生石锤了)
首先必须有的就是一个维度储存当前的位数,还有两个维度就是数的值和每一位数的最小公倍数。
但是要存\(9×10^{18}\)显然不太现实,但是这个数其实和这个数模上\(2520\)是等效的,因此存该数模\(2520\)就可以了。
这时的数组就出来了\(DP[20][2525][2525]\),里面储存的是当前满足情况的\(Beautifu\ Number\)的个数。
一些优化
- 数据量极大,所以将区间改为前缀形式,即\([1,r]-[1,l-1]\);
- 直接开数组是开不下的。在1~2520中,满足是2520的因数的只有48个数字,因此第三维度的大小可以改为48(或者50稳妥一点)
部分代码
/*
MOD:MOD==2520;
now:当前所在位数;
bevalue:now位之前的数%2520;
lcm:now位之前的数的每一位数的最小公倍数;
ismax:当前是否是已知区间内的最大值;
num[]:一个vector,储存每一位的数字;
factor[]:一个数组,储存了离散化后的2520的因数
findlcm:一个函数,求两个数的最小公倍数(应该知道怎么写吧)
*/
long long dfs(int now,int bevalue,int lcm,bool ismax){
if(now==-1)
return bevalue%lcm==0;//如果已经处理完了最后一位,判断该数是否满足条件
if(ismax==0&&dp[now][bevalue][factor[lcm]]!=-1)
return dp[now][bevalue][factor[lcm]];
//如果不是最大值,并且当前情况已经计算过了(初始化都设为-1),直接返回值
long long ans=0;
int maxnum= (ismax==1) ? num[now] : 9;
//如果前几位是最大值情况,那么当前位最大值为这一位的数,否则为9
for(int i=0;i<=maxnum;i++){//从0开始枚举
int nxtbevalue=(bevalue*10+i)%MOD;//计算包含当前位后所需数值的值
int nxtlcm=lcm;
if(i!=0){
nxtlcm=findlcm(nxtlcm,i);
//若当前位置不为0,计算包含当前位后所有位上的数的最小公倍数(如果为零就是原数)
}
ans+=dfs(now-1,nxtbevalue,nxtlcm,ismax&&i==maxnum);
//如果前几位是最大值的情况,而且当前位为最大值时,ismax=1
}
if(ismax==0){//如果不是最大值,记录结果
dp[now][bevalue][factor[lcm]]=ans;
}
return ans;
}
int main(){
......
dfs(num.size()-1,0,1,1)//从后往前
......
}