回顾经典八种背包

# 背包问题
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。
***
***
## 01背包
- 有 $N$ 件物品和一个容量是 $V$ 的背包。**每件物品只能使用一次**

- 第 $i$ 件物品的体积是 v$i$,价值是 w$i$.
***
***


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

int main() {
    int n, V;
    cin >> n >> V;
    vector<int> v(n + 1, 0), w(n + 1, 0);
    for (int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i];
    }
    vector<vector<int>> dp(n + 1, vector<int>(V + 1, 0));
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= V; j++) {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }
    }
    cout << dp[n][V];
    return 0;
}
```
##### 每个人对于背包中每种量的表达方式不一样仅供参考

 $v[i] 表示物品 i 的重量$

 $w[i] 表示物品 i 的价值$

 $dp[i][j] 表示前i个数重量为j时的最大价值$

 $背包作为动规的一种它也有着其独特的转移公式$

$dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i])$
 
 $01背包每种物品只能选一次所以应当从大到小转移$

 $这样每一次转移时就不会出现一个物品被选择了多次的情况发生$
 
```cpp
for (int i = 1; i <= n; i++) {
    for (int j = 0; j <= V; j++) {
        dp[i][j] = dp[i - 1][j];
        if (j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
    }
}
```
  $因为01背包每次调用的都是它前面的最大价值,所以我们可以把dp的前一位去掉$

  $dp[j] 表示重量为j时的最大价值$  

  
  $我们就可以得到转移公式$  
  $dp[j] = max(dp[j], dp[j - v[i]] + w[i])$

  
  以及简化代码

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

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, V, v, w;
    cin >> n >> V;
    vector<int> dp(V + 1, 0);
    for (int i = 1; i <= n; i++) {
        cin >> v >> w;
        for (int j = V; j >= v; j--) {
            dp[j] = max(dp[j], dp[j - v] + w);
        }
    }
    cout << dp[V] << '\n';
    return 0;
}
```
 _01背包完结_ 
 ***
 ***
 ***
## 完全背包
- _有 $N$ 种物品和一个容量是 $V$ 的背包,**每种物品都有无限件可用。**_

- _第 $i$ 种物品的体积是 v$i$ ,价值是 w$i$_
***
***
```cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, V, w, v;
    cin >> n >> V;
    vector<int> dp(V + 1, 0);
    for (int i = 0; i < n; i++) {
        cin >> v >> w;
        for (int j = v; j <= V; j++) {
            dp[j] = max(dp[j], dp[j - v] + w);
        }
    }
    cout << dp[V];
    return 0;
}
```
_相信大家已经发现了,_  
_完全背包与01背包唯一的区别就是_  
_第二层循环由从大到小变成了从小到大_

因为完全背包每一种物品的个数都是无限多个,所以它可以由自己得到自己


_完全背包完结_
***
***
***
## 多重背包
- 有 $N$ 种物品和一个容量是 $V$ 的背包。


- 第 $i$ 种物品最多有 s$i$件,每件体积是 v$i$,价值是 w$i$。
***
***
一种简单粗暴的方式是将 $i$ 个物品直接拆解为$i$种物品  _01背包_ 暴力求解,但这种方式太过暴力,时间复杂度过高,因此我们可以考虑用二进制存储,再用
_01背包_
的方式求解

——我们可以考虑这样储存  
假设有$10$个物品  
那我们可以将它分解为$(2^0 + 2^1 + 2^2 + 3)$  
再采用
_01背包_
的方法


_代码如下:_


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

int main() {
    int n, V, s;
    cin >> n >> V;
    vector<int> v, w;
    v.push_back(0);
    w.push_back(0);
    for (int i = 1; i <= n; i++) {
        int v1, w1;
        cin >> v1 >> w1 >> s;
        int k = 1, s1 = s;
        while (s >= k) {
            s -= k;
            v.push_back(v1*k);
            w.push_back(w1*k);
            k *= 2;
        }
        if (s > 0) {
            v.push_back(v1*s);
            w.push_back(w1*s);
        }
    }
    vector<int> dp(v.size(), 0);
    for (int i = 0; i < v.size(); i++) {
        for (int j = V; j >= v[i]; j--) {
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[V];
    return 0;
}
```
那为什么我们可以用二进制分解呢?


$10$的二进制表示为$(1010)$  
那么$(100) + (10) + (1)$ 再加上 $3$ 的任意组合

是不是就可以将它的所有可能性表示完了呢?

_多重背包完_
***
***
***
## 混合背包
_物品一共有三类:_

- _第一类物品只能用1次(01背包)_;  
- _第二类物品可以用无限次(完全背包);_  
- _第三类物品最多只能用 s $i$ 次(多重背包);_
***
***
这一题就不过多赘述了,把前面的套上就好了。

_代码如下:_
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
    int n, V, w, v, s;
    cin >> n >> V;
    vector<int> dp(V + 1, 0);
    for (int i = 0; i < n; i++) {
        cin >> v >> w >> s;
        if (s == 0) {
            for (int j = v; j <= V; j++) {
                dp[j] = max(dp[j], dp[j - v] + w);
            }
        } else if (s == -1) {
            for (int j = V; j >= v; j--) {
                dp[j] = max(dp[j], dp[j - v] + w);
            }
        } else {
            int v1 = v, w1 = w, sum = 0;
            for (int i = 1; s > 0; i * = 2) {
                int k = min(i, s);
                v1 = v*k;
                w1 = w*k;
                for (int j = V; j >= v1; j--) {
                    dp[j] = max(dp[j], dp[j - v1] + w1);
                }
                s -= i;
            }
        }
    }
    cout << dp[V] << '\n';
    return 0;
}
```
_混合背包完_
***
***
***
## 二维费用背包
- _有 $N$ 件物品和一个 容量是 $V$ 的背包,背包能承受的最大重量是 $M$。_  
- _每件物品只能用一次。体积是 v$i$ ,重量是m$i$ ,价值是 w$i$。_

_求解将哪些物品装入背包,可使物品 总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。_

_输出最大价值。_
***
***
#### 这一题又多了一个量,我们同样用动规的方式解决。  
思考状态和转移方程
_我们可以得到:_

 $dp[i][j] = max(dp[i][j], dp[i - v[i]][j - m[i]] + w[i])$

$v[i]$表示体积,$m[i]$表示重量

_代码如下:_
```cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, V, M;
    cin >> n >> V >> M;
    vector<vector<int>> dp(V + 1, vector<int>(M + 1, 0));
    for (int i = 1; i <= n; i++) {
        int v, m, w;
        cin >> v >> m >> w;
        for (int j = V; j >= v; j--) {
            for (int k = M; k >= m; k--) {
                dp[j][k] = max(dp[j][k], dp[j - v][k - m] + w);
            }
        }
    }
    cout << dp[V][M];
    return 0;
}
```
_二维费用背包完_
***
***
***
## 分组背包
- _有 $N$ 组物品和一个容量是 $V$ 的背包。_

- _每组物品有若干个,同一组内的物品最多只能选一个。 每件物品的体积是 **V**$_i$
$_j$ ,价值是 **W**$_i$
$_j$ ,其中
$i$ 是组号,$j$ 是组内编号。_  

_求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。_

_输出最大价值。_
***
***
因为
_“同一组内的物品最多只能选一个”_
所以每一种状态最多只能存在同组中的一个物品,
因为每一种物品只有一个,


_所以考虑  :_

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

int main() {
    int n, V;
    cin >> n >> V;
    vector<int> dp(V + 1, 0);
    for (int i = 1; i <= n; i++) {
        int s;
        cin >> s;
        vector<int> v(s + 1, 0), w(s + 1, 0);
        for (int j = 1; j <= s; j++) cin >> v[j] >> w[j];
        for (int j = V; j >= 0; j--) {
            for (int k = 1; k <= s; k++) {
                if (j >= v[k]) dp[j] = max(dp[j], dp[j - v[k]] + w[k]);
            }
        }
    }
    cout << dp[V];
    return 0;
}
```
#### 让同组的物品不可能存在于同一种状态里
_分组背包完_
***
***
***
## 有依赖的背包
_有 $N$ 个物品和一个容量是 $V$ 的背包。_

**物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。**

![logo](https://img0.baidu.com/it/u=225831478,977589636&fm=253&fmt=auto&app=138&f=JPEG?w=350&h=248)

- _每件物品的编号是 $i$,体积是v$_i$ ,价值是 w$_i$ ,依赖的父节点编号是 p$_i$ 。物品的下标范围是 $1…N。$_

- _$P_i = -1$,表示根节点。 数据保证所有物品构成一棵树。_

_求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。_ 


_输出最大价值。_

***
***
1. 首先对于输入,设置数组g存储每个点的子节点
2. 考虑将背包与广搜结合,从根节点开始遍历整棵树
3. 考虑采用完全背包
4. 思考转移公式与状态


_——因此我们可以得到 :_

$dp[u][i]=max(dp[u][i],dp[u][i-j]+dp[x][j])$


>u表示当前子树的根节点
>***
>i表示重量
>***
>j表示当前遍历的重量
>***
>x表示当前遍历的子节点
>***
> dp[u][i]表示 以节点u为根节点的子树长度恰好为$i$的最大价值
>***
_代码如下 :_
```cpp
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> g;
vector<vector<int>> dp;
vector<int> v, w;
int n, V;
void dfs(int u) {
    dp[u][0] = 0;
    dp[u][v[u]] = w[u];
    for (int x : g[u]) {
        dfs(x);
        for (int i = V; i >= 0; i--) {
            for (int j = 0; j <= V; j++) {
                if ((i - j) > 0 && dp[u][i - j] != -1) dp[u][i] = max(dp[u][i], dp[u][i - j] + dp[x][j]);
            }
        }
    }
}
int main() {
    cin >> n >> V;
    g = vector<vector<int>>(n + 1, vector<int>());
    dp = vector<vector<int>>(n + 1, vector<int>(V + 1, -1));
    int r;
    v = vector<int>(n + 1);
    w = vector<int>(n + 1);
    for (int i = 1; i <= n; i++) {
        int p;
        cin >> v[i] >> w[i] >> p;
        if (p != -1) g[p].push_back(i);
        else r = i;
    }
    dfs(r);
    int sum = 0;
    for (int x : dp[r]) {
        if (x > sum) sum = x;
    }
    cout << sum;
    return 0;
}
```
_——有依赖背包完_
***

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值