背包问题详解及其拓展问题

背包问题

背包的描述与其初始化和更新的关系

1、体积最多是j

​ 那么体积小于j的时候是合法的,将所有点初始化为0,更新时保证 v ≥ 0 v \ge 0 v0

2、体积恰好是j

​ 那么体积不为j的时候是不合法的,将 v = 0 v = 0 v=0初始化为0,其余体积初始化为正无穷或者负无穷,更新时保证 v ≥ 0 v \ge 0 v0

3、体积至少是j

​ 那么体积至少为0包含了所有非负整数,所以将 v = 0 v = 0 v=0初始化为了0,其余体积初始化为正无穷或者负无穷,更新时不用保证 v ≥ 0 v \ge 0 v0 (在 v < 0 v < 0 v<0时,包含了所有的 v ≥ 0 v \ge 0 v0,所以当体积为负时依旧合法)

一、01背包

01背包就是给定一些物品,每个物品只有一件,通过选定不同物品来达到最大的价值或者方案数

所以每次更新状态的时候,都要从上一层更新,优化为一位数组表示之后就是从大到小遍历

当状态描述为体积至多为某值时,可以求得其最大价值和最大方案数

闫氏DP分析法

在这里插入图片描述

压缩成为一维之后,状态表示为f[j] = max(f[j],f[i-v]+w),易得从大到小遍历体积
模板1——最大价值:

#include<bits/stdc++.h>
using namespace std;

int main()
{
	
	int n,m;//物品数n,背包容量m
	vector<int>a(n),b(n),f(m + 1, 0);
	for(int i = 0;i < n;i++)
		cin >> a[i] >> b[i];//价格a和价值b
	for(int i = 0;i < n;i++)
		for(int j = m;j >= a[i];j--)
			f[i] = max(f[i],f[i - a[i]] + b[i]);
	
	//最后的f[m]就是最大价值
	return 0;
}

模板2——最大方案数:

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n,m;//物品数n,背包容量m
	vector<int>f(m + 1, 0);
    f[0] = 1;
	for(int i = 0;i < n;i++)
    {
        int c;
        cin >> c;
        for(int j = m;j >= c;j--)
            f[j] += f[j - c];
    }
	
	//最后的f[m]就是最大方案数
	return 0;
}

在背包状态描述为体积至少为某值时,可以求得其最小价值

模板3——最小价值:

#include<bite/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f

int main()
{
    int n,m;//物品数n,最小体积m
    vector<int>f(m + 1,INF);
    f[0] = 0;
    for(int i = 0;i < n;i++)
    {
        int v,w;//输入体积和价值
        cin >> v >> w;
        for(int j = m;~j;j--)
            f[j] = min(f[j],f[max(0,j - v)] + w);
    }
    
    //最后的f[m]就是最小价值
    return 0;
}

二、完全背包

完全背包中的每个物品都可以拿无限多个,所以状态更新都是从这一层更新,优化为一维数组之后就是从小到大遍历

在这里插入图片描述

将空间压缩为一维之后,状态表示为f[i] = max(f[i], f[i - v] + w)

模板1——最大价值

#include<bite/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;
    vector<int>f(m + 1, 0);
    while(n--)
    {
        int v,w;
        cin >> v >> w;
        for(int i = v;i <= m;i++)
            f[i] = max(f[i],f[i - v] + w);
    }
    
    //f[m]就是最后的答案
    return 0;
}

模板2——最大方案数

#include<bite/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;
    vector<int>f(m + 1, 0);
    f[0] = 1;
    while(n--)
    {
        int v;
        cin >> v;
        for(int i = v;i <= m;i++)
            f[i] += f[i - v];
    }
    
    //f[m]就是最后的答案
    return 0;
}

三、多重背包问题

在多重背包中,每个物品可以选择有限个

多重背包本质上还是01背包,每种物品中有多个,将每个都是为一种物品

多重背包的朴素实现

模板1——最大价值

#include<bite/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;
    vector<int>f(m + 1, 0);
    for(int i = 0;i < n;i++)
    {
        int v,w,s;//s表示物品数量
        cin >> v >> w >> s;
        for(int j = m;j >= v;j--)
            for(int k = 0;k <= s && k * v <= j;k++)
                f[j] = max(f[j],f[j - k * v] + k * w);
    }
    
    //f[m]就是答案
    return 0;
}

模板2——最大方案数

#include<bite/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;
    vector<int>f(m + 1, 0);
    f[0] = 1;
    for(int i = 0;i < n;i++)
    {
        int v, s;//s表示物品数量
        cin >> v >> s;
        for(int j = m;j >= v;j--)
            for(int k = 0;k <= s && k * v <= j;k++)
                f[j] += f[j - k * v];
    }
    
    //f[m]就是答案
    return 0;
}

多重背包的二进制优化

模板1——最大价值

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;
    vector<pair<int,int>>a;
    vector<int>f(m + 1,0);
    for(int i = 0 ;i < n;i++)
    {
        int v,m,s;
        for(int j = 1;j <= s;s -= j, j *= 2)
            a.push_back({j * v,j * w}); 
        if(s)a.push_back({s * v, s * w});
    }
    
    for(auto g : a)
    {
        for(int i = m;i >= a.first;i--)
            f[i] = max(f[i],f[i - g.first] + g.second);
    }
    
    //f[m]就是答案
    return 0;
}

多重背包的单调队列优化

#include<bits/stdc++.h>
using namspace std;

const int N = 20010;
int f[N],g[N],q[N];//f表示这一层,g表示上一层,q表示滑动窗口

int main()
{
    int n,m;
    cin >> n >> m;
    for(int i = 0;i < n;i++)
    {
        int v,w,s;
        cin >> v >> w >> s;
        memcpy(g,f,sizeof f);
        for(int j = 0;j < v;j++)//表示不同的体积余数,余数相同的为同一组
        {
            int hh = 0,tt = - 1;
            for(int k = j;k <= m;k += v)
            {
                if(hh <= tt && q[hh] < k - s * v)
                    hh++;
                while(hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w)//滑动窗口
                    tt--;//比较两状态的相对大小,不比较后边加的w
                q[++tt] = k;
                f[k] = g[q[hh]] + (k - q[hh]) / v * w;
            }
        }
    }
    
    //f[m]就是答案
    return 0;
}

四、分组背包

在分组背包中,物品被分为多组,但是每组最多只能选一个

在这里插入图片描述

模板——最大价值

#include<bits/stdc++.h>
using namespace std;

int main()
{
   	int n,m;
    cin >> n >> m;
    vint f(m + 1);
   	for(int i = 0;i < n;i++)
    {
        int c;
        cin >> c;
        vint v(c),w(c);
        for(int j = 0;j < c;j++)
            cin >> v[i] >> w[i];
       	for(int j = m;~j;j--)
            for(int k = 0;k < c;k++)
                if(v[k] <= j)
                    f[j] = max(f[j],f[j - v[k]] + w[i]);
    }
    
    
    //f[m]就是答案
    return 0;
}

五、背包问题的拓展

1.01背包+树形DP

假设存在这样的一棵树,想要选择它的子节点,就必须选择他的父节点。每个节点有其体积和价值,求最大体积为v的情况下的最大价值。

1
2
3
4
5
6
7

如图,如果要选择节点4,就必须要选择节点2和节点1(根节点)。

//使用邻接表来存储树

void dfs(int u)
{
	for(int i = h[u];~i;i = ne[i])
	{
		int son = e[i];//表示子节点的编号
        dfs(son);//遍历子节点
        
        for(int j = m - v[u];~j;j--)
        	for(int k = 0;k <= j;k++)
        		f[u][j] = max(f[u][j],f[u][j - k] + f[son][k]);
	}
	
	for(int i = m;i >= v[u];i--)// 加上该节点
		f[u][i] = f[u][i - v[u]] + w[u];
	for(int i = 0;i < v[u];i++)//将该节点不能更新的体积记为0
		f[u][i] = 0;
}

2.最佳方案数

求最大价值的方案数

01背包的最佳方案数

#include<bits/stdc++.h>
using namespace std;

int main()
{
    int n,m;
    cin >> n >> m;
    pair<int,int>f(m + 1,0);//first存储价值,second存储方案数
    f[0].second = 1;
    int res = 0;//记录最大价值
    for(int i = 0;i < n;i++)
    {
        int v,w;
        cin >> v >> w;
        for(int j = m;j >= v;j--)
        {
            int cnt = 0;
            int maxv = max(f[j].first,f[j - v].first + w);
            res = max(res,maxv);
            if(maxv == f[j].first)cnt += f[j].second;
            if(maxv == f[j - v].first + w)cnt += f[j - v].second;
            
            f[j].first = maxv;
            f[j].second = cnt % mod;
        }
    }
    
    int ans = 0;
    for(int i = 0;i <= m;i++)
        if(res == f[i].first)
            ans = (ans + f[i].second) % mod;
   	cout << ans << endl;
}

3.分组背包+排列组合

给定几组物品,每组内进行排列组合之后再按照分组背包的规则进行选择

关于排列组合的方法,这里采用二进制的方式来表示,通过检测某一位上是为1来判断是否选择该物品

#include<bits/stdc++.h>
using namespace std;
#define vint vector<int>

int main()
{
    int n,m;
    cin >> n >> m;//n表示物品组数,m表示背包容积
    vint f(m + 1,0);
    for(int i = 0;i < n;i++)
    {
        int s;//每组背包的物品数量
        cin >> s;
        vint v(s),w(s);
        for(int j = 0;j < n;j++)
            cin >> v[j] >> w[j];
        for(int j = m;j >= 0;j--)
        {
            for(int k = 0;k < 1 << s;k++)
            {
                int V = 0,W = 0;
                for(int u = 0;u < s;u++)
                    if(j >> u & 1)	
                        V += v[u],W += w[u];
                if(j >= V)
                    f[j] = max(f[j],f[j - V] + W);
            }
        }
    }
    
    cout << f[m] << endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值