背包详解

01背包
给定一个容量为c的背包,有n个物品,第i个质量为wi,价值为vi,求背包的最大价值

由于每种物品只有1个,因此每个物品只有01两种状态,即拿和不拿
用V【i,j】表示在面对第i个物品且背包容量为j时,背包内的最大价值

那么显然,V【0,j】和V【i,0】都应该初始化为
解决问题时有两种情况,
(1)当前背包容量不足以放入i(j<wi),那么V【i,j】=V【i-1,j】
(2)当前背包容量足以放入i(j>=wi),此时要选择i放还是不放,如果不放V【i,j】=V【i-1,j】;如果放V【i,j】=V【i-1,j-wi】+vi。取两者之间的那个较大的值
状态转移方程

if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

0123456789101112
00000000000000
10000888888888
200008810101010181818
300668814141616181824
400669914141717191924
500669914141717192124
6026891114161719192124

不需要开二维数组,因为对每个物品操作时只需要上一次的数据,只需一维数组,而且01背包每个物品只有一个,只有取或不取的状态,为了能够保留上次的数据不被改变,所以从向前遍历
核心代码:

  for(i=1;i<=m;i++){  //尝试放置每一个物品
        for(j=t;j>=w[i];j--){//倒叙是为了保证每个物品都使用一次
            f[j]=max(f[j-w[i]]+v[i],f[j]);
            //在放入第i个物品前后,检验不同j承重量背包的总价值,如果放入第i个物品后比放入前的价值提高了,则修改j承重量背包的价值,否则不变
        }
    }

题目:
算法实验4:划分问题
Time Limit: 1 Sec Memory Limit: 128 MB Special Judge
Submit: 1852 Solved: 297
[Submit][Status][Forum]
Description
给定一个正整数的集合A={a1,a2,….,an},是否可以将其分割成两个子集合,使两个子集合的数加起来的和相等。例A = { 1, 3, 8, 4, 10} 可以分割:{1, 8, 4} 及 {3, 10}

Input
第一行集合元素个数n  n <=300 第二行n个整数

Output
如果能划分成两个集合,输出任意一个子集,否则输出“no”

Sample Input
5
1 3 8 4 10
Sample Output
3 10
代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <cstdlib>
#include <algorithm>
#include <queue>
#define ll long long
using namespace std;
int dp[4000000];
int a[305];
int pre[4000000];
int main()
{
    int n,sum=0;
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i],sum+=a[i];
    if(sum%2!=0)
    {
        cout<<"no"<<endl;
        return 0;
    }
    sum=sum/2;
    memset(dp,0,sizeof(dp));
    for(int i=1; i<=n; i++) //
        for(int j=sum; j>=a[i]; j--)
        {
 
            if(dp[j-a[i]]+a[i]>dp[j])
            {
                   dp[j]=dp[j-a[i]]+a[i];
                   pre[j]=i;
            }
        }

    int k,flag=0;
 
    if(dp[sum]!=sum)
    {
        cout<<"no"<<endl;
        return 0;
    }
    cout<<a[pre[sum]];
    sum-=a[pre[sum]];
    while(sum>0)
    {
        cout<<" "<<a[pre[sum]];
        sum-=a[pre[sum]];
 
    }
    cout<<endl;
    return 0;
}

完全背包
完全背包与01背包的区别是完全背包有n个物品,每个物品有无限件,容量是C,物品i的质量为w【i】,价值为v【i】,我们还是以二维数组来进行思考,我们在01背包中每次更新值时面临的决策是要不要放第i件物品,而在完全背包,我们需要抉择的便是,我们此时要放第i件物品几件?(0——最多能放的件数)

完全背包问题与01背包问题的区别在于完全背包每一件物品的数量都有无限个,而01背包每件物品数量只有1个,所以说与它相关的策略已经不是只有取和不取这两种策略了,而是有取0件、取1件、取2件……等等很多种策略

我们可以把把完全背包问题转化为01背包问题来解,第i种物品最多选V/c[i]件,于是可以把第i种物品转化为v/c[i]件费用及价值均不变的物品,然后求解这个01背包问题。

状态转移方程:

if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

每次也需要上一次的值,但是完全背包要正序遍历背包容量,确定在这件物品在比当前数小的容量的背包里已经放了几个了
核心代码:

 for(int i=1; i<=n; i++)
        for(int j=w[i]; j<=V; j++)//注意此处,与0-1背包不同,这里为顺序,0-1背包为逆序
            f[j]=max(f[j],f[j-w[i]]+c[i]);

那么,01背包逆序的原因实际上是因为他要上一状态中的大的那一个,因此需要逆序保留原值,而完全背包要的是当前背包内的状态加上新的物品,因此需要正序。
在这里插入图片描述

多重背包
多重背包的每个物品都有自己的数量限制
多重背包可以采用二进制思想优化,比如有13个a物品,用二进制的思想,可以拆成
1个a物品
2个a物品
4个a物品
还剩6个a物品
各自捆绑,就相当于对这四个物品01背包

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAX 1000000
using namespace std;
 
int dp[MAX];//存储最后背包最大能存多少
int value[MAX],weight[MAX],number[MAX];//分别存的是物品的价值,每一个的重量以及数量
int bag;
 
void ZeroOnePack(int weight,int value )//01背包
{
    int i;
    for(i = bag; i>=weight; i--)
    {
        dp[i] = max(dp[i],dp[i-weight]+value);
    }
}
void CompletePack(int weight,int value)//完全背包
{
    int i;
    for(i = weight; i<=bag; i++)
    {
        dp[i] = max(dp[i],dp[i-weight]+value);
    }
}
 
void MultiplePack(int weight,int value,int number)//多重背包
{
    if(bag<=number*weight)//如果总容量比这个物品的容量要小,那么这个物品可以直到取完,相当于完全背包
    {
        CompletePack(weight,value);
        return ;
    }
    else//否则就将多重背包转化为01背包
    {
        int k = 1;
        while(k<=number)
        {
            ZeroOnePack(k*weight,k*value);
            number = number-k;
            k = 2*k;//这里采用二进制思想
        }
        ZeroOnePack(number*weight,number*value);
    }
}
int main()
{
    int n;
    while(~scanf("%d%d",&bag,&n))
    {
        int i,sum=0;
        for(i = 0; i<n; i++)
        {
            scanf("%d",&number[i]);//输入数量
            scanf("%d",&value[i]);//输入价值  此题没有物品的重量,可以理解为体积和价值相等
        }
        memset(dp,0,sizeof(dp));
        for(i = 0; i<n; i++)
        {
            MultiplePack(value[i],value[i],number[i]);//调用多重背包,注意穿参的时候分别是重量,价值和数量
        }
        cout<<dp[bag]<<endl;
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值