硬币找零(子集型动规)

Description

  FJ到镇上买些补给,为了高效完成任务,他想使硬币转手次数最少。

  FJ想要买T(0<=T<=10000)元钱的东西,有N(1<=N<=100)种不同的硬币流通,面值分别为v[1]..v[N](单位:元),FJ有 C[i ]个面值为 V[i] 的硬币,我们假设店主有无限多的硬币,并总按最优秀方案找零。

Input

  第1行:2个整数N与T。  第2行:N个整数,表示V[1]..V[N]。  第3行:N个整数,表示C[1]..C[N]

Output

  一个整数,表示最优方案的转手次数,如无解输出-1

问题转化一下:FJ有一些硬币,数量有限;店主每种硬币都有,数量无限,求一种方案使FJ出的钱减去店主出的钱恰好等于T,且双方用的硬币尽可能少。

于是可以分开计算,f1[i]表示FJ凑成钱i需要的最少硬币,f2[i]表示店主凑成i需要的最少硬币。等于是做两个背包问题,然后枚举FJ凑出的钱,更新答案即可。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int MAXT=10005;
const int MAXN=105;
const int INF=20000000;
int N,T,c[MAXN],v[MAXN],f1[40005],f2[40005],V[MAXN];
int main()
{
	scanf("%d%d",&N,&T);
	int MAXV=0,P;
	for(int i=1;i<=N;i++)
		scanf("%d",&v[i]),MAXV=max(MAXV,v[i]);
	for(int i=1;i<=N;i++)
	{
		scanf("%d",&c[i]);
		V[i]=V[i-1]+c[i]*v[i];
	}
	
	P=MAXV*MAXV;
	
	for(int i=1;i<=P+T;i++)
		f1[i]=f2[i]=INF;
	
	int i,j,k;
	for(i=1;i<=N;i++)
	for(j=min(P+T,V[i]);j>=v[i];j--)
	for(k=1;k<=min(c[i],j/v[i]);k++)
		f1[j]=min(f1[j],f1[j-k*v[i]]+k);
	
	
	for(i=1;i<=N;i++)
	for(j=v[i];j<=P+T;j++)
		f2[j]=min(f2[j],f2[j-v[i]]+1);
	
	int ans=INF;
	for(int i=P+T;i>=T;i--)
		if(f1[i]<INF&&f2[i-T]<INF)
			ans=min(ans,f1[i]+f2[i-T]);
	
	if(ans<INF)
		printf("%d",ans);
	else
		printf("-1");
	return 0;	
}

猜你喜欢

转载自blog.csdn.net/WWWengine/article/details/81155222