数位DP-x%f(x)-状态变换

题目传送门
题意:
给你一个闭区间,要你求出这个区间满足数值%(各位数字之和)==0数的个数。
思路:
数位DP,首先想到的是3个状态变量确定唯一一个状态,dp[pos][sum][num]-前pos位,数字之和为sum,数值为num。
这种比较易想出,但是由于num的范围太大,会爆内存,所以需要确定另外的状态变量和状态 。
于是另外一种节省内存的DP是:dp[pos][sum][num][mod]-前pos位,当前数字和为sum,数值为num ,最终数字和为mod.

爆内存的DP方式:
#include<bits/stdc++.h>
using namespace std;
int dp[11][82][10000];//i-pos,j-sum,k-num,l-mod
//前pos位,当前数字和为sum,数值为num ,最终数字和为mod 
//由这4个状态变量确定唯一一个状态,状态的选择要无后效性 
//可以有这3个状态变量确定唯一一个状态,但是由于num的范围太大所以需要确定另外的状态 
int d[11];
int m = 0,n = 0;
int dfs(int pos,int sum,int num,bool lim){
	if(pos == 0){
		if(sum == 0){
			return 0;
		}
		return num % sum == 0 ;//
	}
	if(!lim && dp[pos][sum][num] != -1){
		return dp[pos][sum][num];
	}
	int up = lim ? d[pos] : 9;
	int tmp = 0;
	for(int i = 0;i <= up;++i){
		tmp += dfs(pos-1,sum + i,(num*10 + i),lim && i == d[pos]);
	}
	if(!lim){
		dp[pos][sum][num] = tmp;
	}
	return tmp;
}
int solve(int x){
	if(x == 0){
		return 0;
	}
	fill(dp[0][0],dp[0][0]+11*82*10000,-1);
	int tmp = x;
	int cnt = 0;
	while(tmp > 0){
		d[++cnt] = tmp % 10;
		tmp /= 10;
	}
	d[cnt+1] = 0;
	int ans = 0;
	//for(int i = 1;i <= 81;++i){
		ans += dfs(cnt,0,0,true);
	//} 
	return ans;
}
int main(){
	int T = 0,kase = 0;
	scanf("%d",&T);
	while(T--){
		scanf("%d %d",&n,&m);
		printf("Case %d: %d\n",++kase,solve(m) - solve(n-1));
	}
	
	return 0;
}

接下来是另外一种可行性DP:

AC code;
#include<bits/stdc++.h>
using namespace std;
int dp[11][82][82][82];//i-pos,j-sum,k-num,l-mod
//前pos位,当前数字和为sum,数值为num ,最终数字和为mod 
//由这4个状态变量确定唯一一个状态,状态的选择要无后效性 
 
int d[11];
int m = 0,n = 0;
int dfs(int pos,int sum,int num,int mod,bool lim){
	if(pos == 0){
		return num == 0 && sum == mod;//
	}
	if(!lim && dp[pos][sum][num][mod] != -1){
		return dp[pos][sum][num][mod];
	}
	int up = lim ? d[pos] : 9;
	int tmp = 0;
	for(int i = 0;i <= up;++i){
		tmp += dfs(pos-1,sum + i,(num*10 + i) % mod,mod,lim && i == d[pos]);
	}
	if(!lim){
		dp[pos][sum][num][mod] = tmp;
	}
	return tmp;
}
int solve(int x){
	//重复初始化了 
	//fill(dp[0][0][0],dp[0][0][0]+11*82*82*82,-1);
	int tmp = x;
	int cnt = 0;
	while(tmp > 0){
		d[++cnt] = tmp % 10;
		tmp /= 10;
	}
	d[cnt+1] = 0;
	int ans = 0;
	for(int i = 1;i <= 81;++i){
		ans += dfs(cnt,0,0,i,true);
	} 
	return ans;
}
int main(){
	int T = 0,kase = 0;
	scanf("%d",&T);
	memset(dp,-1,sizeof(dp));//只需要初始化一次,因为数字满不满足条件是固定的 
	//不会随测试样例改变 
	while(T--){
		scanf("%d %d",&n,&m);
		printf("Case %d: %d\n",++kase,solve(m) - solve(n-1));
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值