题意:问[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;
}