传送门: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));
}
}
好好理解。