数位dp CodeForces - 55D 美丽数字

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;
    }
}

猜你喜欢

转载自www.cnblogs.com/-Zzz-/p/11415938.html
今日推荐