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;
}