//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面值硬币代替,产生更优方案。 */