usaco 最少找零

Description

约翰在镇上买了 T 元钱的东西,正在研究如何付钱。假设有 N 种钞票,第 i 种钞票的面值为 Vi,约翰身上带着这样的钞票 Ci 张。商店老板罗伯是个土豪,所有种类的钞票都有无限张。他们有洁癖,所以希望在交易的时候,交换的钞票张数尽可能地少。请告诉约翰如何恰好付掉 T 元,而且在过程中交换的货币数量最少。

Input Format

• 第一行:两个整数 N 和 T,1 ≤ N ≤ 100, 1 ≤ T ≤ 10000

• 第二行:n个整数 Vi 第三行:n个整数 Ci,1 ≤ Vi ≤ 120, 0 ≤ Ci ≤ 10000

Output Format

• 单个整数:表示付钱找零过程中交换的最少货币数量,如果约翰的钱不够付账,或老板没法找 开零钱,输出 −1

Sample Input

3 70
5 25 50
5 2 1

Sample Output

3

Hint

约翰付给老板 75 元,老板找约翰 5 元,交换 了 3 张钞票

Source

USACO
读完题目 我们会发现很难用一个动归方程去解决这道问题;
那么我们可以把题目分解成两个问题;
一个是约翰 他的钞票是有限的 那么就把他当作分组背包处理
 1 ≤ T ≤ 10000 所以g[i]表示约翰付i元所用的最少钞票
由于0 ≤ Ci ≤ 10000 则时间效率是(TCN);明显会超时;
于是我把分组背包用01背包来做用了二进制优化(logCTN)
当然用贪心去多余的Ci也能用分组背包做;
而二进制优化明显快得多 只用一百多毫秒
然后就是土豪老板了 把他当作完全背包做就好;
f[i]表示老板付i元所用的最少钞票;
然后循环 T~10000;ans=min(ans,F[I]+G[I-T]);
#include<cstdio>
#include<iostream>
using namespace std;
int f[10010],c[1410],w[1410],g[10010];
int i,j,k,l,m,n,ans,T,n1;
int main()
{
	//freopen("xx.in","r",stdin);
	scanf("%d%d",&n,&T);
	for(i=1;i<=n;++i)
	scanf("%d",&c[i]);
	for(i=1;i<=n;++i)
	scanf("%d",&w[i]);
	for(i=1;i<=10000;++i)g[i]=f[i]=1e9;
	for(i=1;i<=n;++i)
	for(j=0;j<=10000-c[i];++j)
	f[j+c[i]]=min(f[j+c[i]],f[j]+1);
	n1=n;
	for(i=1;i<=n;++i)
	{
		int num=1;
		while(w[i]>num)
		{
			n1++;c[n1]=num*c[i];
			w[n1]=num;w[i]-=num;
			num*=2;
		}
		c[i]=w[i]*c[i];
	}
	for(i=1;i<=n1;++i)
	for(j=10000;j>=c[i];--j)
	g[j]=min(g[j],g[j-c[i]]+w[i]);
	ans=1e9;
	for(i=T;i<=10000;++i)
	if(g[i])
		ans=min(ans,g[i]+f[i-T]);
	if(ans==1e9)ans=-1;
	printf("%d",ans);
}

  

posted @ 2016-11-10 15:48  peter863  阅读(305)  评论(0编辑  收藏  举报