0-1背包 完全背包 多重背包 Dividing poj1014

背包问题:一个背包总容量为w, 现在有n个物品, 第i个物品容量为weight[i], 价值为value[i], 现在往背包里面装东西, 怎样装才能使背包内物品总价值最大。

主要分为3类:

1. 0-1背包, 每个物品只能取0个,或者1个.
2. 完全背包, 每个物品可以取无限次.
3. 多重背包, 每种物品都有个数限制, 第i个物品最多可以为num[i]个.

背包问题多用动态规划求解,动态规划最重要的就是找到递推公式

0-1背包

0-1背包指每一种物品都只有一件,可以选择放或者不放。现在假设有n件物品,背包承重为w。

设置二维数组dp,dp[i][j]表示,背包的容量为j,物品的数量为i的情况下,背包内物品的最大价值。

dp[i][j]分两种情况,第一种情况是不放入第i个物品,dp[i][j] = dp[i-1][j];

第二种情况是放入第i个物品,其中放入第i个物品需要weight[i]的空间,故dp[i][j] = dp[i-1][ j-weight[i] ]+value[i]

此时需要注意 j >= weight[i];

dp[i][j] = max( dp[i-1][j] , dp[i-1][ j-weight[i] ]+value[i] ) ; 当 j >=weight[i]时

dp[i][j] = dp[i-1][j] ; 当j < weight[i]时;

现假设n和w都是非常大的数字,这样二维数组会占用很多的空间,但是,

发现dp[i][j]只和dp[i-1]行有关系,并且只和dp[i-1][<=j]有关系,故转为一维存储

公式变为dp[j] = max(dp[j] , dp[ j-weight[i] ]+value[i]),等号左边的dp[j]为dp[i][j],等号右边的dp[j]为dp[i-1][j];

影响dp[j]的元素为 dp[i-1][=j] 和 dp[i-1][ <j ]

/*
0-1背包问题
这个题目采取动态规划的思想
max dp[num][weight]  num为物品的数量 weight为背包可以容纳的重量
dp[i][j]数组存储的是 i个物品,容量为j的情况下,能容纳的最大价值
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
dp[][0] =0;
dp[0][] = 0;
分析公式,可以发现dp[i][j]只和dp[i-1]行有关系,并且只和dp[i-1][<=j]有关系,故转为一维存储
*/
#include<iostream>
#include<cstring>
using namespace std;
const int maxN = 100;
int main(){
    int n,w;//一共num个物品
    int weight[maxN+1];
    int value[maxN+1];
    cin>>n>>w;
    for(int i = 1; i <= n; i++){
        cin>>weight[i]>>value[i];
    }
    int dp[maxN+1];
    memset(dp,0,sizeof(dp));
    for(int i = 1; i <= n;i++){  //i表示物品
        for(int j = w; j >= weight[i]; j--){  //j表示重量  这个循环必须这样写
            dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
            //注意这个公式
            //j倒着循环,为了防止第i行的dp[j]影响改行后面的dp[j]
            //修改后面的,不会对前面造成影响
         }
    }
    cout<<dp[w]<<endl;
}

这个代码需要注意j的循环方向,解释如下:

            //for example  i = 4,weight[i] = 3 ,j=3时
            //更新了dp[3] = dp[3-3]+value[4],此时的dp[3]为dp[4][3];
            //j=weight[4]+3=6的时候,dp[6] = mnax(dp[6],dp[3]+value[3]),
            //而在这里的dp[3]应该为dp[i-1][3] = dp[3][3],
            //用这个循环方式,其实是完全背包问题...

总结就是dp[j] = max(dp[j] , dp[ j-weight[i] ]+value[i]),等号右边的dp[j]必须为dp[i-1][j],若顺序执行,当dp[j]需要使用上一行的d[j-weight[i]]的时候,可能已经修改过了。

完全背包问题

每个物品可以取无限次.

分析同上

dp[i][j] = max( dp[i-1][j] , dp[i-1][ j- k*weight[i] ]+value[i]*k )   0<=k<=j/weight[i];

化为一维dp[j] = max(dp[j] , dp[ j-weight[i] ]+value[i]),等号左边的dp[j]为dp[i][j],等号右边的dp[j]为dp[i][j];

影响dp[j]的元素为 dp[i-1][=j] 和 dp[i][ <j ]

dp[i][j]表示,背包的容量为j,物品的数量为i的情况下,背包内物品的最大价值。

/*
完全背包问题
这个题目采取动态规划的思想
max dp[num][weight]  num为物品的数量 weight为背包可以容纳的重量
dp[i][j]数组存储的是 i个物品,容量为j的情况下,能容纳的最大价值
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
dp[][0] =0;
dp[0][] = 0;
分析公式,可以发现dp[i][j]只和dp[i-1]行有关系,并且只和dp[i-1][<=j]有关系,故转为一维存储
*/
#include<iostream>
#include<cstring>
using namespace std;
const int maxN = 100;
int main(){
    int n,w;//一共num个物品
    int weight[maxN+1];
    int value[maxN+1];
    cin>>n>>w;
    for(int i = 1; i <= n; i++){
        cin>>weight[i]>>value[i];
    }
    int dp[maxN+1];
    memset(dp,0,sizeof(dp));
    for(int i = 1; i <= n;i++){  //i表示物品
        for(int j = weight[i]; j <= w; j++){  //j表示重量  这个循环必须这样写
            dp[j] = max(dp[j],dp[j-weight[i]]+value[i]);
        }
    }
    cout<<dp[w]<<endl;
}

另外一个版本的递归公式,只是写法不一样

d[i][j] = max(d[i-1][j] , d[i][ j-weight[i] ] + value[i]);

注意这个和0-1背包d[i][j] = max(d[i-1][j] , d[i-1][ j-weight[i] ] + value[i]); 的区别

这两个公式的对比,充分的说明了影响d[i][j]元素中的d[?][<j]到底来自于哪一行

0-1背包的d[i][j]和d[i-1]行有关系,而完全背包的d[i][j]和d[i]行有关系,

故0-1背包dp[j]从大往小修改,完全背包从小往大修改。


多重背包

每种物品都有个数限制, 第i个物品最多可以为num[i]个

首先分析,多重背包问题可以转换成0-1背包问题,把每一个物品展开成num[i]个

如果把全部的该物品i装进去已经超重,此时转换成完全背包问题;否则,转换成0-1背包问题。

采取二进制优化:k取值依次为1 2 4 8....

对于物品i,d[i][j]表示背包的容量为j的情况下,背包内物品的最大价值。

当k=1时,假设存在物品i1,质量为weight[i1] = k*weight[i];d[i1][j] 含有 包含[0,1]个i物品 的情况,并且取了容量为j的时候的最大值,num[i] -= k;从总数中去掉

当k=2时,假设存在物品i2,质量为weight[i2] = k*weight[i];d[i2][j] = max(d[i1][j],d[i1][ j-weight[i2] ]), d[i][j] = d[i2][j]含有包含[0,1,0+2,1+2] = [0,1,2,3]的情况,并且取了容量为j的时候的最大值 num[i]-=k;

当k=4时,假设存在物品i3,质量为weight[i3] = k*weight[i];d[i3][j] = max(d[i2][j],d[i2][ j-weight[i2] ]), d[i][j] =d[i2][j]含有包含[0,1,2,3,0+4,1+4,2+4,3+4]的情况,并且取了容量为j的时候的最大值 num[i]-=k;

等等  直到k >= num[i]

也就是  2^k <= num[i] < 2^(k+1)   k = k*2;

最后一定要记得取k=num[i]的情况,把剩余的num[i]个也放入考虑,其实相当于加入了 [2*(k+1),,,,num[i]+2*(k+1)-1];

/*
多重背包问题
转换成0-1背包和完全背包
*/
#include<iostream>
#include<cstring>
using namespace std;
const int maxN = 6;
const int maxW = 120000;
int dp[maxW+1];
int value[maxN+1], num[maxN+1];
int weight[maxN+1] = {0,1,2,3,4,5,6};
int value[maxN+1] = {0,1,2,3,4,5,6};
int n = 6,w =0;
//weight为物品i的重量,value为物品i的价值
//0-1背包
void zeroOnePack(int weight,int value){
    for(int j = w;j >= weight;j--){
        dp[j] = max(dp[j],dp[j-weight]+value);
    }
}
//完全背包
void completePack(int weight,int value){
    for(int j = weight;j <= w;j++){
        dp[j] = max(dp[j],dp[j-weight]+value);
    }
}
//多重背包
void multiPack(){
    memset(dp,0,sizeof(dp));
    for(int i = 1; i <= n; i++){
        if(num[i]*weight[i] > m){
            Complete_Pack(weight[i],value[i]);
            //如果全装进去超了重量,相当于这个物品是无限的
            continue;
        }
        int k = 1;
        while(k < num[i]){ //改成=应该也一样,但是while过后怎么也得一次0-1背包
            //0-1bag
            ZeroOnePack(k*weight[i],k*value[i]);
            num[i] -= k;
            k*=2;
        }
        ZeroOnePack(num[i]*weight[i],num[i]*value[i]);
    }
} 

另外附上poj上1014问题的解 url = http://poj.org/problem?id=1014;

Description

Marsha and Bill own a collection of marbles. They want to split the collection among themselves so that both receive an equal share of the marbles. This would be easy if all the marbles had the same value, because then they could just split the collection in half. But unfortunately, some of the marbles are larger, or more beautiful than others. So, Marsha and Bill start by assigning a value, a natural number between one and six, to each marble. Now they want to divide the marbles so that each of them gets the same total value. Unfortunately, they realize that it might be impossible to divide the marbles in this way (even if the total value of all marbles is even). For example, if there are one marble of value 1, one of value 3 and two of value 4, then they cannot be split into sets of equal value. So, they ask you to write a program that checks whether there is a fair partition of the marbles.

Input

Each line in the input file describes one collection of marbles to be divided. The lines contain six non-negative integers n1 , . . . , n6 , where ni is the number of marbles of value i. So, the example from above would be described by the input-line "1 0 1 2 0 0". The maximum total number of marbles will be 20000.
The last line of the input file will be "0 0 0 0 0 0"; do not process this line.

Output

For each collection, output "Collection #k:", where k is the number of the test case, and then either "Can be divided." or "Can't be divided.".
Output a blank line after each test case.

思路:

转换成0/1背包,这次的dp[j]中存放的是0/1,表示容量为j的背包可否被完全装满

传统的0/1背包递归公式

dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i]);

故若dp[j - weight[i]] == 1,给dp[j]也赋值为1

若dp[i-1][j]本来就为1,不用管。

若dp[w/2]结果为1,说明容量为w/2的背包可否被完全装满。

这个题也完全可以按照传统的背包问题求解。

#include<iostream>
#include<cstring>
using namespace std;
const int maxN = 6;
const int maxW = 120010;
int dp[maxW+1];
int n = 6,w =0;
int main(){
    int index = 0,k;
    int wTemp[20010];
    while(true){
        w = 0;
        index++;
        int n = 0;//真实的下标
        for(int i = 1; i <= 6; i++){
            cin>>k;
            w += k*i;
            int j = 1;
            while(j <= k){
                wTemp[++n] = i*j;
                k -= j;
                j = j<<1;
            }
            if(k)
                wTemp[++n] = k*i;
        }
        if(w == 0){
            break;
        }
        cout<<"Collection #"<<index<<":"<<endl;
        if(w%2){
            cout<<"Can't be divided."<<endl<<endl;
            continue;
        }
        memset(dp,0,sizeof(dp));
        dp[0] = 1;
        for(int i = 1; i <= n; i++){
            for(int j = w; j >= wTemp[i];j--){
                if(dp[j-wTemp[i]] != 0)
                    dp[j] = 1;
            }
        }
        if(dp[w/2] == 1){
            cout<<"Can be divided."<<endl<<endl;
        }else{
            cout<<"Can't be divided."<<endl<<endl;
        }
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值