传送门: Cash Machine
题意:给你争取要获得的钱数, 货币种类和每种货币的数量, 求在不超过给的钱数的情况下,获得尽量多的钱。
思路:一开始我是用常规动态规划的思路来写这道题的,定义dp[i][j]:用第i种货币所能支付的不超过j的钱数的最大钱数。但在实际写的过程中因为担心空间超限,所以我把它降为一维了。状态转移方程为:dp[j]=max(dp[j],dp[j-k✖dk[i]]+k✖dk[i]);
可这种写法是会超时的,时间复杂度为O(nk✖N✖cash),即10的9次方。我们要想办法优化它,在这里我用了二进制来优化。
我们知道1,2,4…2^n 可以表示1~2的n+1次方减一的所有数,这里就用这个思路来优化。
就像多重背包一样,将num件,价值v,花费cost的物品(v,w)拆分为(v,w),(v×2^1, w× 2^1), (v×2^2, w×2^2),⋯,(x,y) 其中(x,y)是无法被拆分剩下来的余项),如此便可转化为01背包问题。
代码:
1.未优化的code:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
const int M=1e3+5;
int nk[M],dk[M],dp[100005],N,cash;
int main()
{
while(cin>>cash>>N)
{
memset(dp,0,sizeof(dp));
for(int i=1;i<=N;i++)
{
scanf("%d%d",&nk[i],&dk[i]);
}
for(int i=1;i<=N;i++)
{
for(int j=0;j<=cash;j++)
{
for(int k=nk[i];k>=0;k--)
{
if(j>=k*dk[i])
dp[j]=max(dp[j],dp[j-k*dk[i]]+k*dk[i]);
}
}
}
printf("%d\n",dp[cash]);
}
return 0;
}
2.二进制优化后的code:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
int dp[100005],a[105],N,cash;
int main()
{
while(cin>>cash>>N)
{
memset(dp,0,sizeof(dp));
memset(a,0,sizeof(a));
int m=0;
for(int i=1;i<=N;i++)
{
int nk,dk;
scanf("%d%d",&nk,&dk);
for(int k=1;k<=nk;k*=2)
{
nk-=k;
a[m++]=k*dk;
}
if(nk>0) a[m++]=nk*dk;
}
for(int i=0;i<m;i++)
{
for(int j=cash;j>=0;j--)//在这里只能从大到小枚举,具体原因见多重背包问题
{
if (j >= a[i])
dp[j] = max(dp[j - a[i]] + a[i], dp[j]);
}
}
printf("%d\n",dp[cash]);
}
return 0;
}