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 ", 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 ", 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 ",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 ", Hash[126]); memset(dp,-1,sizeof(dp)); while(T--){ cin>>n>>m; cout<<sol(m)-sol(n-1)<<endl; } }