01背包问题
题目:洛谷P1048
AC代码:
#include<stdio.h>
int t[110],v[110];
int dp[1010];
int max(int a,int b)
{return a>b?a:b;}
int main()
{
int T,M;
scanf("%d%d",&T,&M);
int i,j;
for(i=1;i<=M;i++)
scanf("%d%d",&t[i],&v[i]);
for(i=1;i<=M;i++)
for(j=T;j>=t[i];j--)
dp[j]=max(dp[j],dp[j-t[i]]+v[i]);
printf("%d",dp[T]);
return 0;
}
01背包问题思路分析
第i重循环中的dp[j]表示使用前i个物品,在j空间下能获得的最大价值。
对于第i个物品,只有 用 或 不用 两个选择,应选择能使价值最大的一个。
如果不用,j空间的背包还是能装之前i-1个物品中最大的那个值;如果用,j空间的背包中最大值为第i个物品的值加上j-t[i]空间中能装的最大值。
完全背包
题目:洛谷P1616
AC代码:
#include<stdio.h>
#define ll long long
ll t[10010],v[10010];
ll dp[10000010];
ll max(ll a,ll b)
{return a>b?a:b;}
int main()
{
ll T,M;
scanf("%lld%lld",&T,&M);
int i,j;
for(i=1;i<=M;i++)
scanf("%lld%lld",&t[i],&v[i]);
for(i=1;i<=M;i++)
for(j=t[i];j<=T;j++)
dp[j]=max(dp[j],dp[j-t[i]]+v[i]);
printf("%lld",dp[T]);
return 0;
}
完全背包就是在01背包的基础上把内循环的倒序改成了正序
倒序巧妙地使每一个物品只能用一次
比如i=1时,即只用第一个物品时,从T到t[i],都是建立在之前背包为0的基础上,即这个物品只能用一次。
而正序时,从t[i]到T,j=t[i]时可以放一个物品,j=2*t[i]时,之前的dp[j-t[i]]已经有一个物品了,正序时是在之前已经放过这个物品的基础上继续看放还是不放。所以倒序改正序即可。
多重背包
经典的二进制转换法
题目:洛谷P1776
AC代码:
#include<stdio.h>
int v[1000005],w[1000005];//v为价值,w为重量
int dp[1000005];
int max(int a,int b)
{return a>b?a:b;}
int main()
{
int n,W;
scanf("%d%d",&n,&W);//n为宝物种数,W为最大载重
int i,j,cnt=0;
int a,b,c;
for(i=1;i<=n;i++)
{
scanf("%d%d%d",&a,&b,&c);
for(j=1;j<=c;j*=2)
{
v[++cnt]=j*a;
w[cnt]=j*b;
c-=j;
}
if(c!=0)
{
v[++cnt]=c*a;
w[cnt]=c*b;
}
}
for(i=1;i<=cnt;i++)
for(j=W;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d",dp[W]);
return 0;
}
多重背包思路就是转化为01背包。因为数量是固定的,我们很容易想到的思路就是把数量为n的一种物品转化为n个物品来做01判断。但是这样数量太大,会超时。
一种特殊的办法是二进制转化,也就是对数量为n的物品,把它转化成1+2+22+…+2k+剩下的。这样对1~n的每一个数,都能由上面的某些数组合出来。
即把n个物品打包成若干包,每一包数量为上面的数列,每一包算一个物品。这样,不管想选择几个物品放入,都能等价地转化为选择某些包组合出这个数来。
这样就把多重背包转化为了对这些小包的01背包问题。