http://codeforces.com/problemset/problem/55/D
题意: 问从l到r之间有多少个数x满足x%mul(x[i])==0, 其中mul(x[i])表示x各个位上的数的乘积(除0外).
思路:数位dp
写着个题需要两个前置储备:
1: x对一系列的数的乘积(<x)取余 相当于 x对这一系列数的最小公倍数取余.
所有x的x[i]能得出的乘积最大不超过2*2*2*3*3*3*5*7=2520.
所以, 判断 x%mul(x[i])==0 就相当于判断 x%lcm(x[i])==0
2: X%T=Y, T%L=0, Y%L=0 →→→X%L=0, 其中, X为x, T为2520, L为最小公倍数.
所以, 判断 x%mul(x[i])==0 就相当于判断 (x%2520)%lcm(x[i])==0
因此, 只需要一维储存%2520的余数, 第二维储存各位的lcm即可.
然而如果开dp[20][2525][2525]就会MLE..
注意到所有数位的lcm虽然最大是2520, 但远没有2520个, 所以想到离散化.
将[2,2,2,3,3,3,5,7]这些数乘积能构成的数打表,去重后发现只有48个,果断离散化.
代码:
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
#define fuck(x) std::cout<<"["<<#x<<"->"<<x<<"]"<<endl;
using namespace std;
typedef long long ll;
const int M=2e5+5;
const int inf=1e9+5;
const int mod=1e9+7;
//memset(a,0x3f,sizeof(a));
ll dp[20][50][2600];
// pos lcm rmd
int s[20];
int a[48]= {1,2,3,4,5,6,7,8,9,10,
12,14,15,18,20,21,24,28,30,35,
36,40,42,45,56,60,63,70,72,84,
90,105,120,126,140,168,180,210,252,280,
315,360,420,504,630,840,1260,2520
};
int gcd(int a,int b) {
if(a%b==0)
return b;
else
return gcd(b,a%b);
}
int lcm(int a,int b) {
return a*b/gcd(a,b);
}
ll dfs(int pos,int Lcm,int rmd,int lim) {
int LCM=a[Lcm];
//LCM:真实的LCM Lcm:虚假的Lcm
if(pos<=0) {
if(rmd%LCM==0)
return 1;
return 0;
}
if(!lim&&dp[pos][Lcm][rmd]!=-1)
return dp[pos][Lcm][rmd];
int nn=lim?s[pos]:9;
ll ans=0;
for(int i=0; i<=nn; i++) {
int _LCM,_rmd;
if(i==0) {
_LCM=LCM;//0不算
} else {
_LCM=lcm(LCM,i);
}
_rmd=(rmd*10+i)%2520;
int _Lcm=lower_bound(a,a+48,_LCM)-a;
ans+=dfs(pos-1,_Lcm,_rmd,lim&&i==s[pos]);
}
if(!lim)
dp[pos][Lcm][rmd]=ans;
return ans;
}
ll solve(ll x) {
memset(s,0,sizeof(s));
int si=0;
while(x>0) {
s[++si]=x%10;
x/=10;
}
return dfs(si,0,0,1);
}
int main() {
ll n,m;
memset(dp,-1,sizeof(dp));
int _;
scanf("%d",&_);
while(_--) {
scanf("%lld%lld",&n,&m);
printf("%lld\n",solve(m)-solve(n-1));
}
return 0;
}