UVA 674 - Coin Change 完全背包求状态数

传送门:UVA674

题意:用1、5、10、25、50 这五个数去凑给定的数n,问有多少种方案。

个认为这题真的挺好的,如果你理解透彻了,20行就能写出来,但要是抓不到本质就无从下手。

网上关于这题大致有两种解法,一是递推,一是记忆化搜索,其实二者在思想上是相互转化的,也就是DP的根本。

通过做这个题更加深刻理解了这种联系。

递推分析:

完全背包问题。定义状态dp[i,j],代表用前i种硬币组合出金额j时的组合方式。
状态转移方程:dp[i,j] = ∑{ dp(i-1,j-c[i]*k) | 1<=k<=j/c[i], c[i]∈{1,5,10,25,50} }

因为只有顺序不相同的话算一种方案,所以要避免重复,先枚举c[i],然后枚举所有可能的剩余金额,因为我们是按顺序枚举的c[i],所以不会出现重复的情况。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<vector>
#include<map>
#define ll long long
#define pi acos(-1)
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int,int>P;
/*int dp[7500];
int s[5]={1,5,10,25,50};
void init()
{
	dp[0]=1;
	for(int i=0;i<5;i++)
	for(int j=s[i];j<7500;j++)
	dp[j]+=dp[j-s[i]];
}
int main()
{
	int n;
	init();
	while(~scanf("%d",&n))
	{
		printf("%d\n",dp[n]);
	}
}*/
//更易理解的二维dp写法 
#define MAXN 8000 
int type[5] = {1,5,10,25,50}; 
int dp[5][MAXN];//用int类型即可,如果MAXN上万要用unsigned long long 
int n , ans; 
//打表处理好所有的数据 
void solve(){ 
    for(int i  = 0 ; i < 5 ; i++){ 
        for(int j = 0 ; j < MAXN ; j++){ 
            if(i == 0 || j == 0) dp[i][j] = 1;//i为0说明只由1组成那么dp[i][j]为1,j为0说明价值为0的组成方案只有1种(如果看成0则会WA) 
            else dp[i][j] = 0;//其它全部初始化为0 
        } 
    }     
    for(int i = 1 ; i < 5 ; i++){//i-1出现,那么从1开始枚举 
        for(int j = 1 ; j < MAXN ; j++){ 
            for(int k = 0 ; k*type[i] <= j ; k++)//k个type[i],注意必须从0开始 
                dp[i][j] += dp[i-1][j-k*type[i]];//加上前面的方案数 
       } 
    } 
}  
int main(){ 
    //freopen("input.txt" , "r" , stdin); 
    solve(); 
    while(scanf("%d" , &n) != EOF){ 
        for(int i = 4 ; i >= 0 ; i--){ 
            if(dp[i][n]) { ans =  dp[i][n] ; break;}//从末尾开始查找只要有一个不为0就是答案 
        } 
        printf("%d\n" , ans); 
    } 
    return 0; 
}
上面的代码二维是我从网上扒的,感觉二维的更容易理解完全背包DP的本质思想,理解了以后再转化成一维的就不难理解了。

接下来是记忆化搜索:

这可能是最简单的记忆化搜索了,不过这个题要注意去除重复情况,即保证按顺序搜索,若这一层用了c[i],则构成余额的接下来几层搜索只能用c[i],c[i+1],c[i+2]...这样就避免了1,1,5和5,1,1重复的情况

代码:

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<stack>
#include<set>
#include<vector>
#include<map>
#define ll long long
#define pi acos(-1)
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int,int>P;
//dp[i][j]表示只用j到5这几种硬币组成i分钱时的种类数
int dp[7500][5];
int s[5]={1,5,10,25,50};
int dfs(int n,int d)
{
	if(dp[n][d]!=-1)
	return dp[n][d];
	dp[n][d]=0;
	for(int i=d;i<5&&n>=s[i];i++)
	dp[n][d]+=dfs(n-s[i],i);//注意这里下一次dfs从i开始是为了避免1,1,5和5,1,1这样的重复情况 
	return dp[n][d];		//只用更大面值的硬币去凑剩余的金额,保证了不会有重复 
}
int main()
{
	int n;
	memset(dp,-1,sizeof(dp));
	for(int i=0;i<5;i++)
	dp[0][i]=1;
	while(~scanf("%d",&n))
	{
		printf("%d\n",dfs(n,0));
	}
 } 
好好理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值