题目链接 https://www.nowcoder.com/acm/contest/163/J
题意,求[1-n]区间内,一个数可以被该数各位数和整除的个数。(n<=1e12)
题目思路:该题用朴素的思想是会超时的。正解是用数位dp。
什么是数位dp,数位dp是动态规划中的一类问题的解决办法。
这类问题有这样一个特点,即求一个区间内满足某个特征的数的个数。而这个区间可能非常的大。
以至于我们不得不改变传统一个个数出满足该条件数的的方式,来加快对题目求解的速度。
而数位dp的核心思想是用一个或多个状态去压缩这个庞大的区间。把结果放置入压缩后的数组之中。
然后得到结果的过程中,不断记录满足到当前位数下,在某个特定状态下答案的数量,并保存结果,也为以后服务。
每次查询,可以观察是否数组中已经记录了我们要求解的值(某个位数下,某个状态下)。如果有就不再求解。没有的话,就进一步求解,并且求解完毕可以保存在数组中。
这道题,在求解过程中,我们无法知道该数最后可能有几位,以及该数的每位数和最后可能是多少。
因此,我们应该把所有的情况都要列出来。
我们假设
dp[pos][sum][modnow][modn] 它表示,
从最低位到pos位的状态下,各个位数相加为sum的状态下,把modn作为模数的状态下,数对于modn取模的值为modnow的状态下的数的个数。
这个时候,如果设我们要求解的答案为ans,且在模数为modnow状态下的答案为answer[modnow];
那么 ans = answer[1] + answer[2] + ... + answer[108] // 12数相加一定不超过108,其实也不超过105
因此我们最后要求解在各位数和为 1....n 所有情况下的答案。
代码如下:
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
int dig[13];
long long dp[13][110][110][110];//pos sum modnow modn
//pos 当前数位
//sum 加到当前为止的和
//limit 受到上一位的限制
long long dfs(int pos,int sum,int modnow,int modn,int limit){
if(pos==-1){
return sum==modn&&modnow==0;
}
if(!limit&&dp[pos][sum][modnow][modn]!=-1){
return dp[pos][sum][modnow][modn];
}
int now = limit?dig[pos]:9;
long long ans = 0;
for(int i=0;i<=now;i++){
ans += dfs(pos-1,sum+i,(modnow*10+i)%modn,modn,limit&&i==dig[pos]);
}
if(!limit){
dp[pos][sum][modnow][modn] = ans;
}
return ans;
}
long long solve(long long x){
int pos=0;
while(x){
dig[pos++] = x%10;
x/=10;
}
long long ans = 0;
for(int i=1;i<=108;i++){
ans += dfs(pos-1,0,0,i,1);
}
return ans;
}
int main(){
long long n;
int t;
int cases = 0;
scanf("%d",&t);
memset(dp,-1,sizeof(dp));//放在外面,可以供其他使用
while(t--){
scanf("%lld",&n);
printf("Case %d: %lld\n",++cases,solve(n));
}
}