2018 上海大都会赛 J、Beautiful Numbers(数位dp)

题目链接

题意:问[1,N]里多少个数能够被其数位数字之和整除。(N<=1e12)


扯到数位,求区间满足此特征数字的个数,典型数位dp!
我们统计12位数字全为9,也就是这些数的数位和有大小约束,不超过108.
所以这里我们的dp[i][sum][num][mod] 记录的是前12-i为数位的代表的10进制值%mod为sum,后i+1位数位之和为num且是sum变为0的取值方式的个数(即满足题意的数的个数)!看起来有点大,实际上测试T不超过100,且时限8s,证明此解法符合题意!

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <math.h>
#include <set>
#include <map>
#include <vector>
#define llt long long
using namespace std;


llt dp[15][120][120][120];// dp[i][j][k] 记录i层树中前面的数位乘本身进制%k的个数
int dight[50];
llt ddp(int dep,int num,int sum,int limit,int mod){
    if(dep==-1) return sum==0&&num==0;

    llt &k = dp[dep][num][sum][mod];
    if(k!=-1&&!limit) return k;

    llt ans = 0;
    int last = limit?dight[dep]:9;
    for(int i=0;i<=last&&i<=num;++i){
        ans += ddp(dep-1,num-i,(sum*10+i)%mod,limit&&(i==last),mod);
    //cout<<num<<" "<<mod<<" "<<ans<<endl;
    }
    if(!limit) k = ans;
    return ans;
}

llt solve(llt x){
    int dep=0;
    while(x){
        dight[dep++] = x%10;
        x /= 10;
    }

    llt ans = 0;
    for(int i=1;i<=108;++i)
        ans += ddp(dep-1,i,0,1,i);
    return ans;
}
int main(){
    int cas;
    memset(dp,-1,sizeof(dp));
    scanf("%d",&cas);
    for(int k=1;k<=cas;++k){
        llt x;
        scanf("%lld",&x);
        printf("Case %d: %lld\n",k,solve(x));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38786088/article/details/81437699