http://poj.org/gotoproblem?pid=1787
(1)本题为一般的多重背包问题,亮点在于记录路径的方法,开了一个数组 pre[10010] 记录前一步的位置。调用时用 sum[30] 逐个累加即可(当然,这里的调用方法还有其他的各种方法)。
记录:
pre[j]=j-b[i];
调用:
for(i=m;pre[i]!=-1;i=pre[i]) ans[i-pre[i]]++;
(2)多重背包一如既往,用数组 used[10010] 限制,转化为01背包(或是完全背包,只是形式有点像而已,不属于而这任何一类)。
(3)初始化问题:
1)将dp[i] 预处理为0。 dp[i] 表示 i 元最多用多少硬币 ,但它还有另一重意思,即 i 元可以用已有的硬币表示,其值为真和假(0和1)。换而言之,dp[10010] 完成了两个数组才能完成的任务。然而,第一种含义下,dp[0]=0, 第二种含义下, dp[0]=1,存在矛盾,真的要用两个数组才能完成么?
memset(dp, 0, sizeof(dp)); dp[0]=1;
令 dp[0]=1 时有原因的,因为 dp[0] 的第一种含义并不重要,第二种含义与题目息息相关。
2)只需令 pre[0]=-1 即可;
pre[0]=-1;
(4)限制条件问题:
if((!dp[j]||dp[j-b[i]]+1>dp[j])&&dp[j-b[i]]&&used[j-b[i]]+1<=a[i])
如果 dp[j] 未求或是太小,dp[j-b[i]] 已求,并且硬币还够用,则进入循环。
具体代码:
View Code
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; int n, m; int a[5], b[5]={0,1,5,10,25}; int dp[10010], used[10010]; int pre[10010]; int ans[30]; int main() { int i, j, k; while(scanf("%d", &m)!=EOF) { for(i=1;i<=4;i++) scanf("%d", &a[i]); if(!m&&!a[1]&&!a[2]&&!a[3]&&!a[4]) break; memset(dp, 0, sizeof(dp)); memset(pre, 0, sizeof(pre)); dp[0]=1; // 表示 dp[0] 已求过 pre[0]=-1; for(i=1;i<=4;i++) { for(j=0;j<=m;j++) used[j]=0; for(j=b[i];j<=m;j++) { if((!dp[j]||dp[j-b[i]]+1>dp[j])&&dp[j-b[i]]&&used[j-b[i]]+1<=a[i]) //各种限制条件 { dp[j]=dp[j-b[i]]+1; used[j]=used[j-b[i]]+1; pre[j]=j-b[i]; //记录路径,用数组保存前一步的位置 } } } memset(ans, 0, sizeof(ans)); if(dp[m]) { for(i=m;pre[i]!=-1;i=pre[i]) ans[i-pre[i]]++; printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n", ans[1], ans[5], ans[10], ans[25]); } else printf("Charlie cannot buy coffee.\n"); } return 0; }
