洛谷背包问题:P2851 [USACO06DEC]最少的硬币The Fewest Coins(完全背包+多重背包+抽屉原理)

坚强是说给别人听的谎言 提交于 2019-11-28 07:19:13

题目描述

Farmer John has gone to town to buy some farm supplies. Being a very efficient man, he always pays for his goods in such a way that the smallest number of coins changes hands, i.e., the number of coins he uses to pay plus the number of coins he receives in change is minimized. Help him to determine what this minimum number is.

FJ wants to buy T (1 ≤ T ≤ 10,000) cents of supplies. The currency system has N (1 ≤ N ≤ 100) different coins, with values V1, V2, ..., VN (1 ≤ Vi ≤ 120). Farmer John is carrying C1 coins of value V1, C2 coins of value V2, ...., and CN coins of value VN (0 ≤ Ci ≤ 10,000). The shopkeeper has an unlimited supply of all the coins, and always makes change in the most efficient manner (although Farmer John must be sure to pay in a way that makes it possible to make the correct change).

农夫John想到镇上买些补给。为了高效地完成任务,他想使硬币的转手次数最少。即使他交付的硬 币数与找零得到的的硬币数最少。

John想要买价值为T的东西。有N(1<=n<=100)种货币参与流通,面值分别为V1,V2..Vn (1<=Vi<=120)。John有Ci个面值为Vi的硬币(0<=Ci<=10000)。

我们假设店主有无限多的硬币, 并总按最优方案找零。注意无解输出-1。

输入格式

Line 1: Two space-separated integers: N and T.

Line 2: N space-separated integers, respectively V1, V2, ..., VN coins (V1, ...VN)

Line 3: N space-separated integers, respectively C1, C2, ..., CN

输出格式

Line 1: A line containing a single integer, the minimum number of coins involved in a payment and change-making. If it is impossible for Farmer John to pay and receive exact change, output -1.

输入输出样例

输入 #1
3 70  5 25 50  5 2 1
输出 #1
3

说明/提示

Farmer John pays 75 cents using a 50 cents and a 25 cents coin, and receives a 5 cents coin in change, for a total of 3 coins used in the transaction.

分析:

农夫拥有n种面额的货币,对于货币i,其面额为vi,拥有的数目为ci。想要付得起所买的商品,则至少要求所有货币所能产生的价值要大于商品的价格!也就是∑v[i]*c[i]>=T!

否则,就无法购买,我们就解决了输出“-1”的情况。(可以骗骗分诶~)

可以考虑抽象成如下的模型。

提议可以简化成:如何凑出如下的两条线段,使其差最小?

这道题的一个难点在于,得想到把农夫和商家分开考虑,因为农夫的硬币数额有限,可以考虑为一个多重背包问题。而商人则可以考虑为完全背包。设f[i]表示农夫凑出i面额所需的最小硬币数

g[i]表示商人凑出i的min硬币,则答案为ans=min(ans,f[i]+g[i-T])

这个状态转移方程的下界很好找,就是T,那么问题来了,我们找钱数的上界在哪?

这个地方就要用到抽屉原理了!

什么是抽屉原理?

举个例子:(From 百度)

桌上有十个苹果,要把这十个苹果放到九个抽屉里,无论怎样放,我们会发现至少会有一个抽屉里面放不少于两个苹果。这一现象就是我们所说的“抽屉原理”。 抽屉原理的一般含义为:“如果每个抽屉代表一个集合,每一个苹果就可以代表一个元素,假如有n+1个元素放到n个集合中去,其中必定有一个集合里至少有两个元素。” 抽屉原理有时也被称为鸽巢原理。它是组合数学中一个重要的原理 [1]  。

付钱的上界是T+Vmax^2!

如何证明?

我们先假设顾客所给的钱S>T+Vmax^2,也就是说商家需要找的钱面额大于Vmax^2,那么显然,商家所找的硬币数要大于Vmax(因为即使用面额最大的Vmax,也大于)

我们假设商家找钱的硬币序列为{a},其中元素个数为n,有n>Vmax

构造数列{a}的前缀和Sn,使a[n]=S[n]-S[n-1];那么对于{Sn},其中元素个数大于Vmax,由抽屉原理和同余类的知识知:则必存在i<j,使得Vmax | S[j]-S[i],即相差为整数倍;设S[j]-S[i]=k*Vmax

而同理,对于顾客所给的钱,也相应的存在这样的一段序列,他们恰好相差k*Vmax,二者可以相互抵消!从而得到更优解;从而证明,在[0,Vmax^2]的范围内就可以求得最优解!

还有一个优化的点,就是背包的二进制转化形式!

二进制分解:

把件数C

分解成若干个件数的集合,这里面数字可以组合成任意小于等于C

的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可

以用数字的二进制形式来解释

比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以

组合成任意小于等于7 的数,而且每种组合都会得到不同的数

15 = 1111 可分解成 0001 0010 0100 1000 四个数字

如果13 = 1101 则分解为 0001 0010 0100 0110 前三个数字可以组合成

7以内任意一个数,加上 0110 = 6 可以组合成任意一个大于6 小于13

的数,虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种

思想去把多件物品转换为,多种一件物品,就可用01 背包求解了。

AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxT 10000+10
#define maxv 123
#define maxn 100+5
using namespace std;
int n,T,mx=-12000,sum=0,ans=2147483647;
int c[1200],v[1200],f[maxT+maxv*maxv],g[maxT+maxv*maxv];
inline int max(int x,int y)
{
return x>y?x:y;
}
inline int min(int x,int y)
{
return x<y?x:y;
}
int main()
{
cin>>n>>T;
for(int i=1;i<=n;i++)
{
cin>>v[i];
mx=max(mx,v[i]*v[i]);
}
for(int i=1;i<=n;i++)
{
cin>>c[i];
sum+=v[i]*c[i];
}
if(sum<T)
{
cout<<"-1";
return 0;
}
memset(g,0x3f,sizeof(g));
memset(f,0x3f,sizeof(f));
f[0]=0;
g[0]=0;
for(int i=1;i<=n;i++)
for(int j1=v[i];j1<=mx;j1++)
g[j1]=min(g[j1],g[j1-v[i]]+1);

for(int i=1;i<=n;i++)
{
for(int j=1;j<=c[i];j*=2)//这里是二进制背包转化!
{
for(int t=T+mx;t>=j*v[i];t--)
f[t]=min(f[t],f[t-j*v[i]]+j);
c[i]-=j;
}

if(c[i])
for(int t=T+mx;t>=c[i]*v[i];t--)
f[t]=min(f[t],f[t-c[i]*v[i]]+c[i]);
}
for(int i=T;i<=T+mx;i++)
ans=min(ans,f[i]+g[i-T]);
cout<<ans;
return 0;
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!