题目连接:点击打开链接
题目大意:John有N种货币,每种货币有相应的价值V和数量C,他要用仅有的货币去买价值为T的货物,问:付的硬币数目和找回的硬币数目最少为多少个?
解题思路:John的货币是有限的,所以可以用多重背包求解出买价值为T的货物最少要花的货币数目,卖家的货币数目是无限的,所以可以用完全背包计算出卖家找钱的最少数目;最后用两个数组遍历出参与交易最少的货币数就可以了。思路是这样的,但是有个大考点就是:如何确定两个背包的背包上限?我一开始将John获得的最大面值的货币总额作为背包上限,可是Runtime了,后来了解到了鸽笼原理,具体如下:
John的付款数最多为maxv*maxv+m
证明如下:
如果John的付款数大于了maxv*maxv+m,即付硬币的数目大于了maxv,根据鸽笼原理,至少有两个的和对maxv取模的值相等(这个意思应该是:至少maxv+1个硬币对maxv求余,然后余数属于[0,maxv-1]范围,肯定有至少两个硬币的余数相同的),也就是说,这部分硬币能够用更少的maxv来代替。(通俗的来说就是可以用更少的大钱换很多的小钱,金币数就少了)。
于是我把背包数组开到20000...成功过了! 具体代码如下,注释很详细。
代码如下: (有个注释的代码,是我前面提到的导致我RT的地方)
#include
#include
#include
#define INF 999999999
using namespace std;
int dp1[20005],dp2[20005];
int main()
{
int n,m;
while(cin>>n>>m)
{
memset(dp1,0,sizeof(dp1));
memset(dp2,0,sizeof(dp2));
int val[150],num[150];
// int maxnv=0;
for(int i=1; i<=n; i++)
{
cin>>val[i];
//if(maxnv
// maxnv=val[i];
}
// int W=maxnv*maxnv+m;
for(int i=1; i<=n; i++)
cin>>num[i];
for(int i=1; i<=20000; i++)
dp1[i]=dp2[i]=INF;
dp1[0]=dp2[0]=0;
int i,j,k;
for(i=1; i<=n; i++) //用多重背包转化为01背包求解最少付多少硬币
{
for(k=1; k<=num[i]; k*=2)
{
for(j=20000; j>=k*val[i]; j--)
{
dp1[j]=min(dp1[j],dp1[j-k*val[i]]+k);
}
num[i]-=k;
}
for(j=20000; j>=num[i]*val[i]; j--)
{
dp1[j]=min(dp1[j],dp1[j-num[i]*val[i]]+num[i]);
}
}
for(int i=1; i<=n; i++) //用完全背包求解找钱的最少硬币数量
{
for(int j=val[i]; j<=20000; j++)
dp2[j]=min(dp2[j],dp2[j-val[i]]+1);
}
int ans=INF;
for(int i=m; i<=20000; i++)
{
ans=min(ans,dp1[i]+dp2[i-m]);
}
if(ans==INF)
cout<<-1<