上海大都会重现赛 Beautiful Numbers 数位dp

题目链接 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));
		
	}
	
}

猜你喜欢

转载自blog.csdn.net/qq_25955145/article/details/81434700