彻底背包

//poj 3260 The Fewest Coins

/*

题意:John带了n种币值Vi的肯定数量Ci的硬币,而shopkeeper的硬币无限多.
给出T,求John支付的硬币数目加上售货员找零的硬币数目的最小值。若是没法支付T,输出-1 
支付时硬币数量有限制,为多重背包问题. 找零时硬币数量无限制,为彻底背包问题
*/


#include<iostream>        //多重背包和彻底背包
using namespace std;    
int main()    
{
    int n,t,euro[110],num[110],dp[30000],maxn;
    cin>>n>>t;
    int mx=0;
    for(int i=1;i<=n;++i)
    {
        cin>>euro[i];
        mx=max(mx,euro[i]);
    }
    maxn=mx*mx+t;        //上界
    for(int i=1;i<=n;++i)
        cin>>num[i];
    fill(dp,dp+maxn+1,-1);
    dp[0]=0;

    //John付钱  多重背包,经过二进制方法转化为01背包
    for(int i=1;i<=n;++i)
    {
        int k=1,s=num[i];
        while(s>=k)
        {
            for(int j=maxn;j>=euro[i]*k;--j)
            if(dp[j-euro[i]*k]!=-1)
            {
                if(dp[j]==-1)
                    dp[j]=dp[j-euro[i]*k]+k;    //注意是 +k
                else
                    dp[j]=min(dp[j],dp[j-euro[i]*k]+k);
            }
            s-=k;k*=2;    
        }
        for(int j=maxn;j>=euro[i]*s;--j)
            if(dp[j-euro[i]*s]!=-1)
            {
                if(dp[j]==-1)
                    dp[j]=dp[j-euro[i]*s]+s;
                else
                    dp[j]=min(dp[j],dp[j-euro[i]*s]+s);
            }
    }

    //shopkeeper找钱  彻底背包
    for(int i=1;i<=n;++i)    
    {
        for(int j=maxn-euro[i];j>0;--j)            //由于是减,因此要逆序循环
            if(dp[j+euro[i]]!=-1)
            {
                if(dp[j]==-1)
                    dp[j]=dp[j+euro[i]]+1;
                else
                    dp[j]=min(dp[j],dp[j+euro[i]]+1);
            }
    }    

    cout<<dp[t]<<endl;
    return 0;
}

/*

上界为:T+maxValue^2,其中maxValue为最大硬币面值。
证实:反证法。假设存在一种支付方案,John给的钱超过T+maxValue^2, 则售货员找零超过maxValue^2,找的硬币数目超过maxValue个,将其看做一数列,求前n项和sum(n),
根据鸽巢原理,至少有两 个对maxValue求模的值相等,假设为sum(i)和sum(j),i<j,则i+1...j的硬币面值和为maxValue的倍数,
同理,John给的钱中也有 必定数量的硬币面值和为maxValue的倍数,
则这两堆硬币可用数量更少的maxValue面值硬币代替,产生更优方案。

*/