题目:
有n种硬币,面值分别为V1,V2,…Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。1 <= n <= 100,0 <= S <= 100,1 <= Vi <= S。
分析:
将每种面值看作一个点,表示“需要凑足的面值”,本题的初始状态为S,目标状态为0。
最小值:用d(i) 表示从面值为 i 到面值为0使用的最少硬币数。则状态转移方程:d(i) = min{d(i-Vj)+1} ,其中1 <= j <= n。当i = 0时,显然d(i) = 0。 INT_MAX 表示最大整数,表示不可达,本程序采用自定义INF表示不可达。
最大值类似。
代码:
采用递推求最小值:
#include <cstdio>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn = 100 + 5;
const int maxS = 10000 + 5;
const int INF = 1<<30; // 不可达的数量
int V[maxn], // 面值数组
d[maxS]; // d[i]表示从面值为 i到面值为0使用的最少硬币数
int n, S;
void solve() {
d[0] = 0;
for(int i = 1; i <= S; i++) d[i] = INF; // d[i] >= INF 表示 无法从面值i到 0
for(int i = 1; i <= S; i++)
for(int j = 1; j <= n; j++) {
if(i >= V[j]) d[i] = min(d[i], d[i-V[j]]+1); //求最小值
}
}
int main(){
printf("硬币n:");
scanf("%d",&n);
printf("面值分别为:");
for(int i = 1; i <= n; i++) scanf("%d",&V[i]);
printf("面值总数S为:");
scanf("%d",&S);
solve();
if(d[S] >= INF){
printf("impossible !!");
}else{
printf("最少:%d枚",d[S]);
}
return 0;
}
测试结果:
采用递归求最大值:
#include <cstdio>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn = 100 + 5;
const int maxS = 10000 + 5;
const int INF = 1<<30;
int V[maxn], // 面值
vis[maxS], // vis[i] = 0 表示d[i]未计算,vis[i] = 1 表示d[i]已计算
d[maxS]; // d[i]表示从面值为 i到面值为0使用的最少硬币数
int n, S;
int dp(int S) {
if(vis[S]) return d[S];
vis[S] = 1;
int& ans = d[S];
ans = -INF; // 不可达
for(int i = 1; i <= n; i++)
if(S >= V[i]) ans = max(ans,dp(S-V[i])+1);
return ans;
}
int main(){
printf("硬币n:");
scanf("%d",&n);
printf("面值分别为:");
for(int i = 1; i <= n; i++) scanf("%d",&V[i]);
printf("面值总数S为:");
scanf("%d",&S);
memset(vis,0,sizeof(vis));
d[0] = 0; vis[0] = 1; // 初始d[0] = 0
dp(S);
if(d[S] < 0){
printf("impossible !!");
}else{
printf("最大:%d枚",d[S]);
}
return 0;
}