Beautiful numbers CodeForces - 55D
题意:定义能被自己所有位数整除的数字为美丽,给定一个区间,求区间内的美丽数字个数。
分析:首先,可以把限制条件转化为之前所有位数的最大公倍数,将pos,sum,lcm,up当作 dfs的条件和dp下标,然后 dp[ pos ][ sum ][ lca ][ up ] 就代表着 pos 之后的位置全部遍历完后,该 状态取这个sum的最大值。这里要避免一个问题,就是 15 和 12,不可以从 15 的 dp[ pos=1 ][ sum=0 ][ up=1 ] 的记忆直接得到 12 的 dp[ pos=1 ][ sum=0 ][ up=1 ] ,而数位dp又需要这样的记忆化来减少时间复杂度,因此,这里的 up 就有了两个作用,即判断某个位置可以遍历到数字几 和 将15 的dp[ 1 ] 定义为饱和(避开15的dp[1] 和 12的dp[1],而选择15的dp[0] 来记忆退得 12的dp[0] ),那么利用记忆化的时候使用的是 dp[ 0 ] = 10 , 即up=0的上一位,就完美解决了。 并且这样就可以用memset一次,别的都可以用记忆化,大大减少时间复杂度。
然后发现MLE了,原因在于数组开太大 ,而1~9的公倍数为2520,[ sum ]的最大值就可以看成 2520,大的就用取模(有相关数论),所以这个只要开2520。并且,[ lca ]位置很多都没用上,1~9的公倍数就那么多少个,所以就要用到离散化,建立一个Hash数组,在操作dp这个数组的时候,把[ lca ]位置的值用Hash数组转化为 cnt 就行了,这个数字大概就50个。
还可以看到Lcm(a,b)=a/gcd(a,b)*b
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn=32;
const int Mod=2520;
ll n,m;
int dig[maxn];
ll dp[maxn][2550][50][2];
int Hash[2550];
int gcd(int a,int b){
if(b==0) return a;
return gcd(b,a%b);
}
int Lcm(int a,int b){
return a/gcd(a,b)*b;
}
ll dfs(int pos,int sum,int lcm,bool up) {
// printf("state = %d %d %d %d \n", pos,sum,lcm,up);
if(pos<0) return sum%lcm==0;
if(!up&&dp[pos][sum][Hash[lcm]][up]!=-1) {
printf("dp %d %d %d %d = %d\n", pos,sum,lcm,up,dp[pos][sum][Hash[lcm]][up]);
return dp[pos][sum][Hash[lcm]][up];
}
ll res=0;
for(int i=0; i<=9; i++){
if(up==1 && i>dig[pos]) break;
int tlcm=lcm;
if(i) tlcm=Lcm(tlcm,i);
res += dfs(pos-1,(sum*10+i)%Mod,tlcm,up&&i==dig[pos]);
// printf("res=%lld\n",res );
}
if(!up) dp[pos][sum][Hash[lcm]][up]=res;
return res;
}
ll sol(ll x){
if(x<0) return 0;
// memset(dp,-1,sizeof(dp));
int cnt=0;
while(x){
dig[cnt++]=x%10;
x/=10;
}
return dfs(cnt-1,0,1,1);
}
int main(){
int T;
cin>>T;
int cnt=0;
for(int i=1; i<=Mod; i++){
if(Mod%i==0){
Hash[i]=cnt++;
}
}
// printf("Hash=%d\n", Hash[126]);
memset(dp,-1,sizeof(dp));
while(T--){
cin>>n>>m;
cout<<sol(m)-sol(n-1)<<endl;
}
}