问题描述:
有n种硬币,面值分别为V1,V2...,Vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。0 <= n <= 100, 0 <= S <= 10000, 1 <= Vi <= S。
分析:
本题的本质还是DAG上的路径问题。我们把每种面值看作一个点,表示"还需要凑足的面值",则初始状态为S,目标状态为0。若当前的状态i,每使用一个硬币j,状态便转移到i-Vj。这个模型和嵌套矩形一题类似,但也有些明显的不同之处:嵌套矩形中并没有确定路径的起点和终点(可以把任意矩形放在第一个和最后一个),而本题的起点必须是S,终点必须是0。把终点固定之后"最短路"才是有意义的。在嵌套矩形中,最短序列显然是空(如果不允许空的话,就是单个矩形,不管怎样都是平凡的),而本题的最短路径却不是那么容易确定的。
接下来考虑"硬币问题"。注意到最长路和最短路的求法是类似的,下面只考虑最长路。由于终点固定,d(i)的确切含义变为"从节点i出发到节点0的最长路径长度"。
下面是求最长路的代码(错误的):
int dp(int S)//错误 { int& ans=d[S]; if(ans>=0) return ans; ans=0; for(int i=1;i<=n;i++)if(S>=V[i]) ans=max(ans,dp(S-V[i])+1); return ans; } /*此代码有一个致命的错误,即由于节点S不一定真的能到达节点0,所以需要用特殊的d[S]值表 示"无法到达",但在上述代码中,如果S根本无法继续往前走,返回值是0,将被误以为是"不能 走已经到达终点"的意思。*/
正确的解法一:
int dp(int S) { int& ans=d[S]; if(ans != -1) return ans; ans=-1<<30; for(int i=1;i<=n;i++)if(S>=V[i]) ans=max(ans,dp(s-V[i])+1); return ans; }
注意:在记忆化搜索中,如果用特殊值表示"还没有算过",则必须将其和其他特殊值(如无解)区分开。
正确的解法二:
int dp(int S) { if(vis[S]) return d[S]; vis[S]=1; int& ans=d[S]; ans=-1<<30; for(int i=1;i<=n;i++)if(S>=V[i]){ ans=max(ans,dp(S-V[i])+1); } return ans; }
尽管多了一个数组,但可读性增强了许多:再也不用担心特殊值之间的冲突了,在任何情况下,记忆化搜索的初始化都可以用memset(vis,0,sizeof(vis))执行。
本题要求最小、最大两个值,记忆化搜索就必须写两个。在这种情况下,还是递推来得更加方便(此时需注意递推的顺序):
min[0]=max[0]=0; for(int i=1;i<=n;i++) { min[i]=INF; max[i]=-INF; } for(int i=1;i<n;i++) for(int j=1;j<=v;j++) if(i>=V[j]) { min[i]=min(min[i],min[i-V[j]]+1); max[i]=max(max[i],max[i-V[j]]+1); } printf("%d %d\n",min[S],max[S]);
完整代码:
#include "stdio.h" #define INF 1<<30 #define maxn 100+10 int V[maxn],n; int min[maxn],max[maxn]; inline int Min(int a,int b){return a<b?a:b;} inline int Max(int a,int b){return a>b?a:b;} //打印可行的方案 void print_ans(int* d,int S){ for(int i=1;i<=n;i++){ if(S>=V[i] && d[S]==d[S-V[i]]+1){ printf("%d ",V[i]); print_ans(d,S-V[i]); break; } } } int main() { int S; //输入面值S和面值的种数n while(~scanf("%d%d",&S,&n)) { for(int i=1;i<=n;i++){ scanf("%d",&V[i]); } max[0]=0; min[0]=0; for(int i=1;i<=S;i++) { min[i]=INF; max[i]=-INF; } //递推实现 for(int i=1;i<=S;i++){ for(int j=1;j<=n;j++){ if(i>=V[j]){ min[i]=Min(min[i],min[i-V[j]]+1); max[i]=Max(max[i],max[i-V[j]]+1); } } } print_ans(min,S); printf(" min\n"); print_ans(max,S); printf(" max\n"); printf("min:%d max:%d\n",min[S],max[S]); } return 0; }
硬币问题——固定终点的最长路和最短路,布布扣,bubuko.com
时间: 2024-10-27 10:43:47