您的位置:首页 > 其它

01背包问题小结

2011-05-12 21:12 183 查看
这两天做了下01背包的题,简单的总结下

首先,对于最基本的01背包问题,转移方程为dp[i][j]=max{dp[i-1][j],dp[i-1][j-cost[i]]+w[i]}

表示考虑第i件物品,容量为j时,有两种策略,第一种是不选该物品,第二种为选择该物品,在这两种策略中选择一个最优策略

复杂度O(VN)

伪代码

for i=1 to n

for v=0 to V

if(v>=cost[i])

dp[i][v]=max(dp[i-1][v],dp[i-1][v-cost[i]]+w[i])

如果用滚动数组压缩空间:

for i=1 to n

for v=V to 0

if(v>=cost[i])

dp[v]=max(dp[v],dp[v-cost[i]]+w[i])

注意到将容量从大到小开始循环后,就可以用一维的数组表示了,当然,这得当所有物品容量都为正时才是正确的

对于01背包问题,每种物品只考虑一次,且每种物品只能被加入一次,而这也恰是为什么第二层循环中v要从大到小的原因,因为当物品容量为正时,容量较大的情况由容量较小的情况决定,如果从小的开始推,一中物品就有可能被加入背包多次

一维的代码中dp[v]在被更新前就相当于dp[i-1][v],我们将容量从大到小开始推的话,设v1>v2,我们更新v1时,v2还未被更新,表示二维中的dp[i-1][v],一旦被更新后,就表示dp[i][v]了,如果我们从小往大推的话,会有可能出现这样的情况:dp[v2]被物品i更新一次,更新dp[v1]时又由dp[v2]与背包i更新一次,这样,dp[v1]保存的信息中,物品i就被装了两次,这与01背包的定义是不同的

当然,如果此时物品容量为负时,我们必须将v从小到大推,道理是一样的

POJ 3132 Sum of Different Primes

求一个数n由k个不同素数组成的方案数,其中,5=2+3和5=3+2是一样的,即不考虑顺序

这道题可以转化成01背包求解,n小于等于1120,k<=14,我们可以先打表打出1120内的素数,然后把这187个素数看成187件物品,然后他们的和看成容量,

即1<=n<=187,1<=V<=1120

但是,这道题又多了一个维数的限制,即装入物品个数的限制,没关系,递推就行

dp[i][j][k]表示前i个素数中的j个素数组成和为k的方案数,那么dp[i][j][k]=sum{dp[i-1][1..j-1][k-prime[i]]

压缩空间后可以去掉一维i,但是k要从大到小枚举,因为不能用重复的素数!

代码:

#include<iostream>
#include<memory.h>
#include<string>
#include<cstdio>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<vector>
#include<map>
using namespace std;
bool flag[1200];
int prime[1200],p_num;
int dp[15][1121];
void init()
{
memset(flag,false,sizeof(flag));
flag[1]=true;
p_num=0;
for(int i=2;i<=1120;i++)
{
if(!flag[i])
prime[p_num++]=i;
for(int j=i*2;j<=1120;j+=i)
{
flag[j]=true;
}
}
}
int main()
{
int i,j,k;
init();
memset(dp,0,sizeof(dp));
//cout<<p_num<<endl;
dp[0][0]=1;
for(i=0;i<p_num;i++)
{
for(k=1120;k>=0;k--)
{
for(j=14;j>=1;j--)
{
if(k>=prime[i]&&dp[j-1][k-prime[i]])
dp[j][k]+=dp[j-1][k-prime[i]];
}
}
}
while(scanf("%d%d",&i,&j)!=EOF)
{
if(i+j==0)
break;
int ans=0;
printf("%d/n",dp[j][i]);
}
return 0;
}


POJ 2184 Cow Exhibition

也可以转化为01背包来做,只是这题需要一些变化,把s看成容量,f看成价值
则dp[i][j]=选前i头牛且s的和为j时,f的最大值,最后枚举满足条件的s和与它对应的f,由于i可能为负,需要增加del值平移
当然,也可以压缩为一维的,但是需要分情况考虑fi大于0和小于0的情况,保证第i头牛只被加1次
还有,这题可以设置i的左右边界优化下,我从200多跑到63ms了
可以参考:http://blog.csdn.net/chendanche/archive/2011/04/12/6317909.aspx
代码:
#include<iostream>
#include<memory.h>
#include<string>
#include<cstdio>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<vector>
#include<map>
using namespace std;
const int MAX=200005;
const int del=100000;
const int inf=1<<30;
int dp[MAX];
int main()
{
int i,j,k,n,s,f,l,r;
for(i=0;i<=200000;i++)
dp[i]=-inf;
dp[0+del]=0;
l=0;
r=0;
scanf("%d",&n);
for(i=0;i<n;i++)
{
scanf("%d%d",&s,&f);
if(s<0)
{
for(j=l;j<=r;j++)
{
k=j+del;
if(k+s>=0&&dp[k]!=-inf)
{
//cout<<i<<" "<<k+s-del<<endl;
dp[k+s]=max(dp[k+s],dp[k]+f);
}
}
l+=s;
}
else
{
for(j=r;j>=l;j--)
{
k=j+del;
if(k+s<=200000&&dp[k]!=-inf)
{
//cout<<i<<" "<<k+s-del<<endl;
dp[k+s]=max(dp[k+s],dp[k]+f);
}
}
r+=s;
}
}
int ans=0;
for(i=0+del;i<=200000;i++)
{
if(dp[i]>=0)
ans=max(ans,i+dp[i]);
}
printf("%d/n",ans-del);
return 0;
}

POJ 1837 Balance
与2184差不多,不过这题求的是方案数,又与3132相似
首先定义状态方程为dp[i][j]为选前个重物且力矩为j时的方案数,则dp[i][j]=sum{dp[i-1][j-pos[k]]},1<=k<=m,m为挂钩的个数,pos[k]为第k个挂钩的位置
那么这题能否也压缩一下呢?答案是不行的,因为这题转移时需要在同一个维度i枚举不同的挂钩,不管你考虑pos[k]为正时倒着来也好,pos[k]为负时正着来也好,都有可能将第i个挂钩多次装入背包,与上面两个题可以放一起体会体会
最后结果为dp
[0+del]

代码:
#include<iostream>
#include<memory.h>
#include<string>
#include<cstdio>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<vector>
#include<map>
using namespace std;
const int del=7500;
int dp[21][del*2+5],w[100],pos[100];
int main()
{
int i,j,k,n,m;
while(scanf("%d%d",&m,&n)!=EOF)
{
for(i=1;i<=m;i++)
scanf("%d",&pos[i]);
for(i=1;i<=n;i++)
scanf("%d",&w[i]);
memset(dp,0,sizeof(dp));
dp[0][0+del]=1;
for(i=0;i<n;i++)
{
for(k=-7500;k<=7500;k++)
{
if(!dp[i][k+del])
continue;
for(j=1;j<=m;j++)
{
dp[i+1][k+w[i+1]*pos[j]+del]+=dp[i][k+del];
}
}
}
printf("%d/n",dp
[0+del]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: