您的位置:首页 > 其它

背包问题(恰好背满 二维背包) 总结

2014-11-05 09:30 183 查看
专题训练:点击打开链接 密码:JXFEACM

他人总结:点击打开链接 背包九讲:点击打开链接

其实核心记住:

背包是组合问题 填充性质 元素之间没关系

1. 01背包(右->左)

恰好装满(仅初始化[0])否则全为0,有负值(右移) 浮点数(调换w,v),次优(两个dp),K优([i-1][v][1...k] 放/不放都存起来然后归并) ,求w+v最大 (一样)

2. 完全背包 不限量,但依照V还是有限制(左->右)多重背包 都用标记数组方法,coins poj1742。

3. 混合背包 HDU 3535

最多一个(每次比较都是上个阶段放入与当前阶段不放,初始化为上一阶段),最少一个(init设定-1,若当前阶段-1则放入,否则取最优),无所谓(01)

4. 二维费用背包 两个属性,多一层循环。eg: 01背包限制为仅取M个物品,用M外循环,表示N个物品填充使得M个最优。dp[m][j] = dp[m-1][j-v[i]]+w[i];

5. 分组背包 混合背包差不多 hdu 1712 n个组,每个组n个物品,进行选取 至多一个。 hdu 3033 至少一个



. “物品” 可以是规则 poj1170

关键字:

动态转移方程

自底向上

自上而下

备忘录

分解子问题

学习DP,背包问题似乎是永远绕不过去的。背包问题其实是多阶段状态填充。结合昨晚因为输出看错WA了一晚上的 POJ1384 (完全背包)来谈。

一、0-1背包

问题:有个容量为V大小的背包,有很多不同重量weight[i](i=1..n)不同价值value[i](i=1..n)的物品,每种物品只有一个,想计算一下最多能放多少价值的货物。

每个物体i,对应着两种状态:放入&不放入背包。背包的最优解是在面对每个物体时选择能够最大化背包价值的状态。0-1背包的状态转移方程为
一维数组从右向左迭代就代表从上一次的i-1转化过来。

0-1背包恰好背满

这个问题有时会碰到,(POJ 1384就是如此)与不要求装满背包的区别就是在初始化的时候,对于没有要求必须装满背包的情况下,初始化最大价值都为0,是不存在非法状态的,所有的都是合法状态,因为可以什么都不装,这个解就是0,但是如果要求恰好装满,则必须区别初始化,除了f[0]=0,其他的f[1…v]均设为-∞或者一个比较大的负数来表示该状态是非法的。

这样的初始化能够保证,如果子问题的状态是合法的(恰好装满),那么才能得到合法的状态;如果子问题状态是非法的,则当前问题的状态依然非法,即不存在恰好装满的情况。

二、完全背包

就是一个物品可以用多次,区别使用就是一维数组从左到右迭代即可。因为同一物品可以用多次,同样是对该状态操作。

二进制拆分。将物品容量与背包容量比较化为 1,2,4,8......n-sigma(w)。这样的0-1背包形式。

逆向思维,将容量放在外层循环。一个物品可以用多次,状态因容量而变化。

二维费用(HDU 3496 ,POJ 2576)

二维费用的背包问题
有N件物品,每件物品,具有两种不同的费用,最大费用分别为V1和V2;
选择这件物品必须同时付出这两种代价;
第i件物品所需的两种代价分别为c1[i]和 c2[i],物品的价值为w[i]。
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+w[i]}

二维费用就是要求满足两种需求不再单单是w了。 3496 这道题刚开始没看出来N个中选M个也是一个“费用”。

这道题我想的是用替换方法去做,即先放入M个,完后每次再用后面的一个去替换,遍历M个中最优替换方案,然后替换之。

MyCode:

#include<math.h>
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 10000000
#define maxn 10+100
typedef __int64 ll;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
int N,M,K,L;
int dp[maxn][110];

struct P
{
int v, w;
}p[maxn];
bool cmp(const struct P &a,const struct P &b)
{return a.w<b.w;}
int main()
{
int i,j,T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&N,&M,&L);
for(i=1;i<=N;i++)
scanf("%d%d",&p[i].w,&p[i].v);
sort(p+1,p+1+N,cmp);
int tag;int ans=0;
memset(dp[1],0,sizeof(dp[1]));
for(i=1;i<=M;i++)
{
for(j=L,tag=1;j-p[i].w>=0;j--)
dp[1][j]=dp[1][j-p[i].w]+p[i].v,ans=max(ans,dp[1][j]),tag=0;
if(tag)break;
}
if(tag){puts("0");continue;}
for(i=2;i<=M;i++)
memcpy(dp[i],dp[1],sizeof(dp[1]));
for(i=M+1;i<=N;i++)
{
for(j=L;j-p[i].w>=0;j--)
{
for(int k=1;k<=M;k++)//同一维
{
if(p[k].w>j)continue;
if(j-p[k].w+p[i].w>L)continue;
if(  dp[k][j-p[k].w+p[i].w]< dp[k][j-p[k].w]+p[i].v)
dp[k][j-p[k].w+p[i].w]= dp[k][j-p[k].w]+p[i].v;
ans=max(ans,dp[k][j-p[k].w+p[i].w]);
}

}
}
printf("%d\n",ans);
}
return 0;
}


WA的原因:

假设 M+1 的时候 用 M + 1 替换了 1 , 改变的只有dp[1]这个数组

然后 M + 2 的时候用 M + 2 替换了 2, 只会 改变 dp[2] 这个数组

你没办法保证同时 用 M + 1 替换 1, M + 2 替换 2

正确的方法则是将m-1个物品看作

AC:

#include<iostream>
#include<cstdio>
using namespace std;
#define INF -999999999
#define N 200005
#define pos 100000
#define max(a,b) (a)>(b)?(a):(b)
int dp
;
int minn,maxn;
int main()
{
int n,i,j,s,t,ans;
while(scanf("%d",&n)!=EOF)
{
for(i=0;i<N;i++) dp[i]=INF;
dp[pos]=0;
minn=maxn=pos;
for(i=0;i<n;i++)
{
scanf("%d%d",&s,&t);
if(s>0)
{
for(j=maxn;j>=minn;j--)
dp[j+s]=max(dp[j+s],dp[j]+t);
maxn=maxn+s;
}
else
{
for(j=minn;j<=maxn;j++)
dp[j+s]=max(dp[j+s],dp[j]+t);
minn=minn+s;
}
}
ans=0;
for(i=pos;i<=maxn;i++)
if(dp[i]>=0) ans=max(ans,i-pos+dp[i]);
printf("%d\n",ans);
}
return 0;
}


HDU 3033

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define maxn 10500
int dp[11][maxn];
int N,M,K;
struct P{
int w,v,id;
friend bool operator < (P x,P y){
return x.id < y.id;
}
}p[110];
int main(){
while(~scanf("%d%d%d",&N,&M,&K)){
for(int i = 0;i < N;i++)
scanf("%d%d%d",&p[i].id,&p[i].w,&p[i].v);
sort(p,p+N);
int cnt = 0;
memset(dp,-1,sizeof(dp));
for(int i = 0;i <= M;i++) dp[0][i] = 0;
for(int i = 0;i < N;i++){
if(p[i].id != cnt) cnt++;
for(int j = M;j >= p[i].w;j--){
if(dp[cnt][j-p[i].w] !=-1) dp[cnt][j] = max(dp[cnt][j],dp[cnt][j-p[i].w] + p[i].v);
if(dp[cnt-1][j-p[i].w] !=-1) dp[cnt][j] = max(dp[cnt][j],dp[cnt-1][j-p[i].w] + p[i].v);
}///上面两句如果换位置,则w为0的元素会加两次
}
if(dp[K][M] == -1) printf("Impossible\n");
else printf("%d\n",dp[K][M]);
}
///之前没有每次都memset dp -1, 以为在做的过程中cnt++ 那里会改,但是最后判断Impossible就出问题了,

return 0;
}
/**
5 10000 3
1 4 6
2 5 7
3 4 99
1 55 77
2 44 66

3 10000 2
1 2 3
1 3 4
1 5 6

5 10000 3
1 1000 3
1 1000 4
2 100000 4
3 1000 3
3 1000 4
**/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: