黄海 1 year ago
commit 6f93d34893

@ -79,54 +79,38 @@ $f(i,j,k)$ 表示从前$i$个物品中选,且花费$1$**不少于**$j$,花费$
有普通的二维费用背包问题中,$j,k$是不能进行超载的,超过了背包就太重, 背包就 **漏** 了!
在本题中**可以超载** 的,理解一下超载是什么意思:
> - $j$:氧气还需缺少$j$升
> - $k$:氮气还需缺少$k$升
> 举栗子:$j=2,k=5$,就是氧气还需要$2$升,氮气还需要$5$升,现在出现的某个气瓶,氧气$20$升,氮气$50$升,一个就可以把你的需求满足,那么请问:你还需要氧气多少升、氮气多少升呢?
> **答**:不需要,都可以满足要求了,即$j=0,k=0$,也就是$f[i-1][0][0]+w$,而对于一个无欲无求的$f[i-1][0][0]$自然是等于$0$,也就是$f[i][j][k]=w$
在本题中是 **可以超载** 的,理解一下超载是什么意思:
> - $j$:氧气还需$j$升
> - $k$:氮气还需$k$升
**举栗子**$j=2,k=5$,就是氧气还需要$2$升,氮气还需要$5$升,现在出现的某个气瓶,氧气$20$升,氮气$50$升,一个就可以把你的需求满足,那么请问:你还需要氧气多少升、氮气多少升呢?
### 三、三维朴素解法
**答**:不需要,都可以满足要求了,即$j=0,k=0$,也就是$f[i-1][0][0]+w$,而对于一个无欲无求的$f[i-1][0][0]$自然是等于$0$,也就是$f[i][j][k]=w$
### 三、三维解法
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const int M = 110;
int n, m, m1, m2;
int f[N][M][M];
int n, m1, m2;
//二维费用01背包-不少于维度费用,求最小代价
int main() {
//注意次序
scanf("%d %d %d", &m1, &m2, &n);
//求最小值
cin >> m1 >> m2 >> n;
memset(f, 0x3f, sizeof f);
f[0][0][0] = 0;
for (int i = 1; i <= n; i++) {
int v1, v2, w;
scanf("%d %d %d", &v1, &v2, &w);
cin >> v1 >> v2 >> w;
for (int j = 0; j <= m1; j++)
for (int k = 0; k <= m2; k++) {
//不选择i号物品
f[i][j][k] = f[i - 1][j][k];
//分情况讨论
//物品i加上就够一维使用此时只关心二维情况即可
if (j - v1 < 0 && k - v2 >= 0)
f[i][j][k] = min(f[i][j][k], f[i - 1][0][k - v2] + w);
//物品i加上就够二维使用此时只关心一维情况即可
else if (j - v1 >= 0 && k - v2 < 0)
f[i][j][k] = min(f[i][j][k], f[i - 1][j - v1][0] + w);
//如果选择了i号物品两个维度直接拉满那么只选择一个i就足够用它参选的价值是w
else if (j - v1 < 0 && k - v2 < 0)
f[i][j][k] = min(f[i][j][k], w);
else
//正常递推
f[i][j][k] = min(f[i][j][k], f[i - 1][j - v1][k - v2] + w);
f[i][j][k] = min(f[i - 1][j][k], f[i - 1][max(0, j - v1)][max(0, k - v2)] + w);
}
}
printf("%d\n", f[n][m1][m2]);
cout << f[n][m1][m2] << endl;
return 0;
}
```

@ -1,16 +1,12 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const int M = 110;
int n, m, m1, m2;
int f[N][M][M];
int n, m1, m2;
// 二维费用01背包-不少于维度费用,求最小代价
int main() {
// 注意次序
cin >> m1 >> m2 >> n;
// 求最小值
memset(f, 0x3f, sizeof f);
f[0][0][0] = 0;
@ -19,24 +15,10 @@ int main() {
cin >> v1 >> v2 >> w;
for (int j = 0; j <= m1; j++)
for (int k = 0; k <= m2; k++) {
// 不选择i号物品
f[i][j][k] = f[i - 1][j][k];
// 分情况讨论
// 物品i加上就够一维使用此时只关心二维情况即可
if (j - v1 < 0 && k - v2 >= 0)
f[i][j][k] = min(f[i][j][k], f[i - 1][0][k - v2] + w);
// 物品i加上就够二维使用此时只关心一维情况即可
else if (j - v1 >= 0 && k - v2 < 0)
f[i][j][k] = min(f[i][j][k], f[i - 1][j - v1][0] + w);
// 如果选择了i号物品两个维度直接拉满那么只选择一个i就足够用它参选的价值是w
else if (j - v1 < 0 && k - v2 < 0)
f[i][j][k] = min(f[i][j][k], w);
else
// 正常递推
f[i][j][k] = min(f[i][j][k], f[i - 1][j - v1][k - v2] + w);
f[i][j][k] = min(f[i - 1][j][k], f[i - 1][max(0, j - v1)][max(0, k - v2)] + w);
}
}
printf("%d\n", f[n][m1][m2]);
cout << f[n][m1][m2] << endl;
return 0;
}

@ -34,14 +34,21 @@ $0<v_i,w_i,s_i≤100$
### 二、分析过程
- 状态表示:$f[i][j]$
集合:从前$i$个物品中选,总体积不超过$j$的所有选法
属性:$max($集合中每种选法总价值$)$
- 状态计算
集合划分的过程,和完全背包很像,但不像完全背包有无穷多个,而是有数量限制
$$f[i][j]=max(f[i][j],f[i-1][j-w[i]*k]+v_i*k) \ \ k \in \{0,1,2,3,...\}$$
<center><img src='https://cdn.acwing.com/media/article/image/2021/06/17/55909_ba412471cf-IMG_4AD8EC65CFE1-1.jpeg'></center>
* **状态表示**
集合:所有只从前$i$个物品中选,并且总体积不超过$j$的选法
属性:集合中每一个选法对应的总价值的最大值
* **状态计算**
就是一个集合划分的过程,就是和完全背包很像,但不像完全背包有无穷多个,而是有数量限制
* 初始状态:`f[0][0]`
* 目标状态:`f[n][m]`
#### 状态转移方程
$$\large f[i][j] = max\{(f[i-1][j k*v[i]] + k*w[i])   |  0 ≤ k ≤ s[i],j>=k*v[i]\}$$
### 三、实现代码(二维数组)
```cpp {.line-numbers}

@ -0,0 +1 @@
<mxfile host="Electron" modified="2024-03-12T01:53:17.907Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.9.6 Chrome/89.0.4389.128 Electron/12.0.16 Safari/537.36" etag="fN_8LHEoFL5IPJiHtDTa" version="14.9.6" type="device"><diagram id="CjEDbDRbTRP9UjkVRqXU" name="第 1 页">5VxNj6M4FPw1lnYP2wJsg32EhPTMoU992DMTnAQNCREhk/Qc9rev7ZgEcFrqlZY8FPd8yDzDI9Qru1xAB+HZ9vxaZ/vNW5WLEgVefkZ4joLAJ0GA1F8v/7hEGDaBdV3kZqdb4L34LUzQM9FjkYtDb8emqsqm2PeDy2q3E8umF8vqujr1d1tVZf+s+2wtrMD7Mivt6N9F3mxM1A/5reObKNYbc2oWRJeObdbubK7ksMny6tQJ4RThWV1VzaW1Pc9EqcBrcbkct/ik9/rBarFrvnLA24rXm49f/PXb0affjz/Z2yv5y2T5lZXH/gUfmo8WAplGoi03ktOmaMT7PluqnpMsuIxtmm0pt3zZzA77SwlWxVnIsyarateYksqy48ScTNSNOH96Ff4VG0kqUW1FU3/IXcwBxKBp6ORHZvt0Kw41oU2nLG0sM3RYXxPfEJMNA9p/ADCwAAymDaDvTwxBbCFIJo4gmxiC1EIQj4jgqijLWVVWtU6MV1T9kfFDU1c/Racn1D8mQyd++fl/anEFdSq1aEdXb0ZNMZKcYbIRoXiOeKganKKYonSBkhniMUpDxGaIcXm9M5lgJgfBjMl/fij/w8HLywtKGUoWiHO1b+yhJNJpYsSIisisie5KsMqt8nHEMUqpTtyeSrZlJJa9gT4qRVxGCEoYYnPdRRBftJGkbXimEccBoumul4wx/aGoOmHMO4deksWqkXiI6+vnCUr052Wpvn7rYw6ZK8nR9CnaZ9qu2okBLU0oK4v1Tm6WYqUyKKIVUtljE94WeV5+NiLq6rjLFf/n3kOEjNi89T1+h7nBaMy11wJBh7m6qElquMcjXV3JZe9KYUn9lsQtB2TpZXHnSJLmRlV7DNhUvTK05ZKbrPADeFrYKxx8pYX3j+91poLrLCJLLEf3hTVzxRc5damC+rrEkY6Y6aRzOEcMq/3VPgsUXyahRDOEqWwxU5nlvCEnk0ueRM+bimCdmdRNqgQhPFXspdyYbmK4EGFLsVzeW4j8YJRQbxTUsQ+93iB3Zu0nw3xoWeBBt1fcY3oWGNDZ1EAP7wjRk4E+tDPwoEcW6N4ftifoaPd1DcD1EoH/OYIeLyWcop6cIvt8UL2AWtUj7KGKzCAVOaeC5eTemGHBD6xvDYygyOB3ALiDigwNejuCnFJkcNDtOwbPr8jgoNt+3J7Sn1Zi8XDiYbbE3ivHaAobuOd5CYYeAqCe9zGrmqHCwoPuoOeFB91BzwsPuu157enFGYUlBFph3fOwhEMPAVAPC6Sw0KC3855TCgsOuoMeFh5028PaoDujsNQDVljsnoeld56WP3YIOPjcFh50UA/7oGUNmxroDnpYeNBtD2sz3R2FjaAV1gEPGwZ90MHfXcAOPIgNh68cQIPerrOeWWKjcGqgO2BiGZ4a6LaJHeOXFyYqsewL7zo9VGKJAybWkljolxGIA09iLYkFB90BF2tJLDjoDrhYS2LBQbddbOiwxEK/60QcdLHkzi9mPnYMOPAodiix4KC3iV2SWHjQHXSx8KDbLjZyV2IJBZZYCupiYST2q8+/xxsDDjyLHUosPOgOulh40EFd7GNAH0osPOi2i2XuSiy985Umj5VYUBcLc6OYQt/JoQ66WHDQW/VxSmLBQQd1sUASCw667WK5wxILfaM4dNDFhtB3ckIHn8XCg+6gi4UH3UEXCw+67WLbN7Bc1NhwvDvFcvP2rfC6r/Pd+jj9Fw==</diagram></mxfile>

@ -1,25 +1,72 @@
##[$AcWing$ $5$. 多重背包问题 II](https://www.acwing.com/problem/content/description/5/)
### 一、与朴素版本的区别
### 一、题目描述
有 $N$ 种物品和一个容量是 $V$ 的背包。
第 $i$ 种物品最多有 $s_i$ 件,每件体积是 $v_i$,价值是 $w_i$。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出**最大价值**。
**输入格式**
第一行两个整数,$NV$,用空格隔开,分别表示物品种数和背包容积。
接下来有 $N$ 行,每行三个整数 $v_i,w_i,s_i$,用空格隔开,分别表示第 $i$ 种物品的体积、价值和数量。
**输出格式**
输出一个整数,表示最大价值。
**数据范围**
$0<N1000$
$0<V2000$
$0<v_i,w_i,s_i2000$
**提示**
本题考查多重背包的二进制优化方法。
**输入样例**
```cpp {.line-numbers}
4 5
1 2 3
2 4 1
3 4 3
4 5 2
```
**输出样例**
```cpp {.line-numbers}
10
```
### 二、与 多重背包问题 $I$ 的区别
区别在于数据范围变大了:现在是三个循环数据上限分别是$1000$(物品种数),$2000$(背包容积),第$i$种物品的体积、价值和数量的上限也是$2000$,原来的每个数字上限都是$100$
三重循环的话,计算次数就是 $1000 * 2000 * 2000=4000000000=4 * 1e9 =40$亿次
解法$I$使用的是三重循环,计算次数就是 $1000 * 2000 * 2000=4000000000=4 * 1e9 =40$亿次
$C++$一秒可以算$1e8$次,就是$1$亿次,$40$亿肯定会超时!
### 二、二进制优化
### 三、二进制优化
**$Q$:怎么来优化呢?**
答:我们先来思考一下为什么方法一的速度慢,因为三层循环:
① 第一层遍历每个物品
② 第二层遍历每个可用的空间
③ 第三层枚举当前物品使用了几个
**$Q$:我们最终的目标是什么?**
答:每个物品选择了几个才是最优的,价值最大的。
朴素多重背包做法的本质:将有数量限制的相同物品看成多个不同的$0-1$背包。
**办法**
**用二进制思想把所有可能的组合都表示出来,转化为$01$背包问题!**
<font color='blue' size=4><b>优化思路:</b></font>
比如我们从一个货车搬百事可乐的易拉罐(因为我爱喝不健康的快乐水~),如果存在$200$个易拉罐,小超市本次要的数量为一个小于$200$的数字$n$,搬的策略是什么呢?
假如$A$物品,有$10$个,你最后要几个不一定,可能是$0$个,$1$个,$2$个,....$10$个。
$A$、一个一个搬,直到$n$为止。
我们可以这样打包:
$B$、在出厂前打成$1$个一箱,$2$个一箱,$4$个一箱,$8$个一箱,$16$个一箱,$32$个一箱,$64$个一箱,乘下$73$个,不够下一轮的$128$个了,<font color='red'><b>该怎么办呢?剩下的打成$73$个一箱!</b></font>
![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/202403120953334.png)
>为什么要把剩下的$73$个打成一个包呢?不是再分解成$64$,$32$这样的组合呢?这是因为本质是化解为$01$背包,一来这么分解速度最快,二来可以表示原来数量的任何子集。
上面这样的转化,就是把一个个尝试,转化为了成批说事,比如上面圆圈里的$4$,理解为$i$物品$4$个打成一包,体积是原来的$4$倍,价值也是原来的$4$倍。这个打包完成的新物品,你可以选择,也可以不选择,当你最终方案中$i$物品要了$5$个时,这个打包$4$就被选中了,当你最终方案中$i$物品要了$2$个时,这个打包$4$就被放弃了
打包,就是成批讨论,避免了一个个讨论,组团,按$yxc$的说法就是集合,不管怎么样吧,二进制打包法将极大加快计算速度!理由:比如$INT\_MAX$,其实就是$2^{31}$,也就是划分成了最多$31$个包,能不快吗!就是打包费点劲。
### 三、一维实现代码 <font color='red' size=4><b>【推荐】</b></font>
```cpp {.line-numbers}

@ -1,4 +1,4 @@
## [AcWing 6. 多重背包问题 III](https://www.acwing.com/problem/content/6/)
## [$AcWing$ $6$. 多重背包问题 $III$](https://www.acwing.com/problem/content/6/)
### 一、题目描述
有 $N$ 种物品和一个容量是 $V$ 的背包。
@ -40,22 +40,8 @@ $0<v_i,w_i,s_i≤20000$
10
```
### 二、多重背包的前世今生
[$AcWing 4$. 多重背包问题 I](https://www.acwing.com/problem/content/4/)
[$AcWing 5$. 多重背包问题 II](https://www.acwing.com/problem/content/5/)
[$AcWing 6$. 多重背包问题 III](https://www.acwing.com/problem/content/6/)
### 三、空间问题
下面将讨论此问题的三种解法,特别说明的是,二维最好理解,而且空间范围也是在可以接受的范围内,不必盲目追求一维,性能上不会带来提升。以最终极版本的单调队列优化算法来说,需要的二维空间最大值就是$f[N][M]$,其中$N*M=1000\times 20000=20000000$,换算成空间大小就是$$\large 1000\times 20000\times4/1024/1024=76MB$$,一般题目的空间限制都是$128MB$左右,再加上$C++$程序运行需要的一部分内存,是可以正常通过测试的,事实上二维方法,在[$AcWing$ $6$. 多重背包问题 $III$](https://www.acwing.com/problem/content/6/) 中,是可以正常$AC$的。
即使题目限制了内存大小最多为$64MB$(这就很$BT$了),也可以简单的使用滚动数组的方法优化,$$\large 2\times 20000\times4/1024/1024=16MB$$
足够过掉此题,一维限制无意义,也不做为讲解的重点,此文只关注二维实现,文末将附上一维实现办法。
### 四、三种解法
<font color='red' size=4><b>三种解法的根本区别在于数据范围,题面都是一样的:</b></font>
### 二、三种解法如何选择
三种解法的根本区别在于数据范围,题面都是一样的:
<!-- 让表格居中显示的风格 -->
<style>
@ -74,192 +60,10 @@ $0<v_i,w_i,s_i≤20000$
| $n≤100$,$V≤100$ | $n≤1000$,$V≤2000$ | $n≤1000$,$V≤20000$ |
</div>
> **温馨提示**~~其实啊,三种都需要熟练背下来,谁知道考试时出题人会从哪个版本出发搞你~~
<center><img src='https://cdn.acwing.com/media/article/image/2021/06/17/55909_ba412471cf-IMG_4AD8EC65CFE1-1.jpeg'></center>
* **状态表示**
集合:所有只从前$i$个物品中选,并且总体积不起过$j$的选法
属性:集合中每一个选法对应的总价值的最大值
* **状态计算**
就是一个集合划分的过程,就是和完全背包很像,但不像完全背包有无穷多个,而是有数量限制
* 初始状态:`f[0][0]`
* 目标状态:`f[n][m]`
#### 状态转移方程
$$\large f[i][j] = max\{(f[i-1][j k*v[i]] + k*w[i])   |  0 ≤ k ≤ s[i],j>=k*v[i]\}$$
### 四、朴素算法
#### 二维朴素
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N][N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
int v, w, s;
scanf("%d %d %d", &v, &w, &s);
for (int j = 0; j <= m; j++)
for (int k = 0; k <= s && v * k <= j; k++)
f[i][j] = max(f[i][j], f[i - 1][j - k * v] + w * k);
}
printf("%d\n", f[n][m]);
return 0;
}
```
#### 一维朴素
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N];
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
int v, w, s;
scanf("%d %d %d", &v, &w, &s);
for (int j = m; j >= v; j--)
//注意此处k=0,k=1是一样的
//如果不要i物品 即 f[i][j]=f[i-1][j]
//转为一维表示法就是f[j]=f[j],所以从0从1都一样
for (int k = 0; k <= s && k * v <= j; k++)
f[j] = max(f[j], f[j - v * k] + w * k);
}
printf("%d\n", f[m]);
return 0;
}
```
在可以考虑第$i$个物品时,前面$i-1$个物品已经做出了选择,前面怎么选择的我不管,我只管我现在面临的情况该怎么处理:
$
\large \left\{\begin{array}{l}
第i个物品一个也不选择 & \\
第i个物品一个选1个& \\
第i个物品一个选2个& \\
... & \\
第i个物品一个选s_i个&
\end{array}\right.
$
当然,你也不能真的一定从$0$选择到$s_i$个,因为可能你的背包装不上了,需要加上限制条件:$v*k<=j$
### 五、二进制优化
朴素多重背包做法的本质:将有数量限制的相同物品看成多个不同的$0-1$背包。
优化的思路:比如我们从一个货车搬百事可乐的易拉罐(因为我爱喝不健康的快乐水~),如果存在$200$个易拉罐,小超市本次要的数量为一个小于$200$的数字$n$,搬的策略是什么呢?
A、一个一个搬直到$n$为止。
B、在出厂前打成$64$个一箱,$32$个一箱,$16$个一箱,$8$个一箱,$4$个一箱,$2$个一箱,$1$个一箱,<font color='red'>**最后剩下的打成$73$个一箱**</font>
为什么要把剩下的$73$个打成一个包呢?不是再分解成$64$,$32$这样的组合呢?这是因为我们其实本质是化解为$01$背包,一来这么分解速度最快,二来可以表示原来数量的任何子集,这样就$OK$了!
#### 二维进制版本
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[N][M]; //二维数组版本AcWing 5. 多重背包问题 II 内存限制是64MB
//只能通过滚动数组或者变形版本的一维数组直接二维数组版本MLE
//多重背包的二进制优化
int main() {
scanf("%d %d", &n, &m);
int idx = 0;
for (int i = 1; i <= n; i++) {
int a, b, s;
scanf("%d %d %d", &a, &b, &s);
//二进制优化,能打包则打包之1,2,4,8,16,...
int k = 1;
while (k <= s) {
idx++;
v[idx] = a * k;
w[idx] = b * k;
s -= k;
k *= 2;
}
//剩下的
if (s > 0) {
idx++;
v[idx] = a * s;
w[idx] = b * s;
}
}
n = idx; //数量减少啦
// 01背包
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
printf("%d\n", f[n][m]);
return 0;
}
```
#### 一维数组二进制版本
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
//多重背包的二进制优化
int main() {
scanf("%d %d", &n, &m);
int cnt = 0;
for (int i = 1; i <= n; i++) {
int a, b, s;
scanf("%d %d %d", &a, &b, &s);
//二进制优化,能打包则打包之1,2,4,8,16,...
int k = 1;
while (k <= s) {
cnt++;
v[cnt] = a * k;
w[cnt] = b * k;
s -= k;
k *= 2;
}
//剩下的
if (s > 0) {
cnt++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt; //数量减少啦
// 01背包
for (int i = 1; i <= n; i++)
for (int j = m; j >= v[i]; j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
printf("%d\n", f[m]);
return 0;
}
```
### 六、单调队列优化
### 三、单调队列优化
使用朴素版本利用数据进行调试,找一下规律,看看哪个状态间存在转移关系:
```cpp {.line-numbers}
@ -294,7 +98,25 @@ int main() {
```
<center><img src='https://img2022.cnblogs.com/blog/8562/202201/8562-20220126104409857-886115013.png'></center>
#### 原理解析
多重背包:物品个数是复数个,但又不是无限个。
- 当作$01$背包来理解,$s$个物品当成$s$次$01$背包操作。然后优化的话通过 **二进制** 来优化。
- 当作完全背包来理解,就是有数量限制的完全背包,而这个数量限制就可以理解成 **滑动窗口的长度**,然后优化通过 **单调队列** 来优化。
队列的单调性就是基于
$$f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w,.....,f[i - 1][j - k * v] + k * w$$
要将前$i - 1$个物品的方案基础上不停尝试放入第$i$个物品,遍历取最大值。
$f[i - 1][j - k*v] + k *w$表示,总空间是$j$,有且仅有$k$个物品$i$,其余空间通过前$i - 1$个物品填充的最大价值。
多重背包因为有数量限制,向前遍历的个数$k$是受到数量$s$限制的。
所以要将$max$中的每个元素$f[i - 1][j], f[i - 1][j - v] + w,.....,f[i - 1][j - k * v] + k * w$,通过维护单调队列,来获得当前窗口宽度$s$范围内的最大值。
并且在$j = j + v$后,队列中所有元素对应状态与当前背包空间差增加了$v$,可以多放一个物品$i$,每个元素对应的价值增加$w$,全部都加一个$w$,所以单调性不发生任何变化。
两种优化可以理解成两种思路的进化路线。
#### 二维版本
```cpp {.line-numbers}
@ -307,34 +129,37 @@ const int M = 20010; // 背包容量上限
int n, m;
int f[N][M]; // 前i个物品在容量为j的限定下最大的价值总和
int q[M]; // 单调优化的队列
int q[M]; // 单调优化的队列,M是背包容量上限说明q[]里面保存的是体积
// 二维朴素版+队列[k-s*v,k],队列长s+1
// 二维+队列[k-s*v,k],队列长s+1
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) { // 枚举每个物品
for (int i = 1; i <= n; i++) { // 考虑前i种物品
int v, w, s; // 体积、价值、个数
cin >> v >> w >> s;
for (int j = 0; j < v; j++) { // ,
// 查找指定范围内的最大值,标准的单调队列
int hh = 0, tt = -1;
for (int k = j; k <= m; k += v) { // 分组内枚举每个可能的体积
// 1、超出窗口范围的队头出队列左侧只保留到k-s*v
// 下面的j,k是一起用来描述剩余体积的,之所以划分成两层循环是因为依赖的前序是按v为间隔的依赖并且是有个数限制的依赖
// j:按对体积取模分组0表示剩余空间除以当前物品的体积余数是0
// k:分组内的每一个体积,注意:这里的体积不一定都是合法的,因为数量是有限制的
// 单调队列的意义查找前面k-s*v范围内的价值的最大值是一个单调递减的队列队头保存的是获取到最大值的最近体积
for (int j = 0; j < v; j++) { //
int hh = 0, tt = -1; // 全新的单调下降队列
for (int k = j; k <= m; k += v) { // 与j一起构成了有效体积
// 1、讨论到第i个物品时由于它最多只有s个所以有效的转移体积最小是k-s*v,更小的体积将被去除
if (hh <= tt && q[hh] < k - s * v) hh++;
// 2、处理队尾,下一个需要进入队列的是f[i-1][k],它是后来的,生命周期长,可以干死前面能力不如它的所有老头子,以保证一个单调递减的队列
while (hh <= tt && f[i - 1][q[tt]] + (k - q[tt]) / v * w <= f[i - 1][k]) tt--;
// 3、k入队列
q[++tt] = k;
// 4、上面操作完f[i-1][k]已经进入队列,f[i][k]需要的所有人员到齐,可以直接从队头取出区间最大值更新自己
// 4、队列维护完毕f[i-1][k]已经进入队列,f[i][k]可以直接从队头取出区间最大值更新自己
f[i][k] = f[i - 1][q[hh]] + (k - q[hh]) / v * w;
}
}
}
printf("%d\n", f[n][m]);
return 0;
}
```
@ -372,23 +197,9 @@ int main() {
}
```
### 七、疑问解答 
#### $Q1$:为什么可以引入单调队列对多重背包进行优化?
$A$:因为朴素版本三层循环,太慢了,要想办法优化?怎么优化的呢?因为发现每个新值要想更新$f[i][j]$值,第$i$件物品,最多有$s_i$件,我们可以选择$0 \sim s_i$个,同时,由于$i$物品的体积是$v_i$,也就是我们在拿物品$i$时,有一个关系
### 七、疑问与解答 
<div class="center">
| | 拿$0$个 | 拿$1$个 | 拿$2$个 | ... | 拿$s$个 |
| ---- | ----------- | --------------- | ------------------- | --- | ------------------- |
| 体积 | $k$ | $k-v$ | $k-2*v$ | ... | $k-s*v$ |
| 价值 | $f[i-1][k]$ | $f[i-1][k-v]+w$ | $f[i-1][k-2*v]+2*w$ | ... | $f[i-1][k-s*v]+s*w$ |
</div>
**总结**
* 往前最多看$s$个
* $f[i][j]$ <font color='red' size=4><b> 跳跃性依赖 </b></font>于$f[i-1][j - x * v]$,想要求什么呢?求离我距离最多$s$个数的最大值。这数不用每次现去查找,可用单调队列动态维护来优化查询。
#### $Q2$:单调队列中装的是什么?
#### $Q_1$:单调队列中装的是什么?
$A$:是体积,是$f[i][j]$可以从哪些 **体积** 转移而来。比如当前$i$物品的体积是$v_i=2$,个数是$3$,那么$f[i][j]$可以从
$$
\large \left\{\begin{array}{l}
@ -398,19 +209,21 @@ $$
f[i-1][j-v_i*3]+3*w_i& 选择3个
\end{array}\right.
$$
转移而来,当然,还需要判断一下是不是你的背包能装下那么多,一旦装不下了就别硬装了。
转移而来,也就是$(j-v_i*0,j-v_i*1,j-v_i*2,j-v_i*3)$这四个中的一个。
当然,还需要判断一下是不是你的背包能装下那么多,一旦装不下了就别硬装了。
#### $Q3$:只记录体积怎么计算最大价值?
$A$:只记录了所关联的体积,最大价值是现用现算的,办法是
#### $Q_2$:只记录体积怎么计算最大价值?
$A$:只记录了所关联的体积,最大价值是现算:
$$\large f[i][k] = f[i - 1][q[hh]] + (k - q[hh]) / v * w$$
即,自己的最优解,可以通过前序当中**最大值**所在的体积`q[hh]`转移而来,产生的增量价值是 $\large \displaystyle (k - q[hh]) / v * w$
即,自己的最优解,可以通过前序当中 **最大值** 所在的体积`q[hh]`转移而来,增量价值是 $\large \displaystyle (k - q[hh]) / v * w$
#### $Q4$:单调队列的使用场景在哪里?
$A$:使用单调队列的唯一场景就是 **离我在$X$的范围内,最大或最小值是多少**?
#### $Q_3$:单调队列的使用场景在哪里?
$A$:使用单调队列的 **唯一场景** 就是 **离我在$S$的范围内,最大或最小值是多少**?
它的任务是做到$O(1)$的时间复杂度进行快速查询结果,所以,只能是放在队首,不能再进行遍历或者二分,那样就不是$O(1)$了。
#### $Q5$:单调队列是怎么样做到将最优解放到队首的呢?
#### $Q_4$:单调队列是怎么样做到将最优解放到队首的呢?
$A:$单调队列优化有三步曲,按套路做就可以完成这样的任务:
* 将已经超过 **窗口范围** 的旧数据从单调队列中去掉,保证窗口中只有最近的、最多$s$个(或$s+1$,这和具体的题意有关,后续会继续说明~)有效数据。
@ -419,13 +232,13 @@ $A:$单调队列优化有三步曲,按套路做就可以完成这样的任务
* <font color='red' size=4><b>滑动窗口是建立在前序数组$f[i-1]$上的,范围只能是前面一行$f[i-1][j],f[i-1][j-v],f[i-1][j-2v],...,f[i-1][j-kv]$</b></font>
#### $Q6:$此处的单调队列,是递增还是递减的?
#### $Q_5:$此处的单调队列,是递增还是递减的?
$A:$是一个单调递减的队列,队列头存储的是窗口中的最大值所对应的体积。
#### $Q7$:为什么要先进队列,再更新答案呢?我看有些同学是先更新答案,再进队列啊?
#### $Q_6$:为什么要先进队列,再更新答案呢?我看有些同学是先更新答案,再进队列啊?
$A$:这个主要看$f[i-1][k]$是不是可以成为答案的备选项,如果是,那么就先进队列,再更新;如果不是,则先更新再进队列。以本题为例,$f[i][k]$可不可以从$f[i-1][k]$迁移而来呢?从实际含义出发,是可以的,这表示:第$i$个物品一个也不要,在空间最大是$k$的情况下,最大值如何表示?此时,当然最大值表示为$f[i-1][k]$了,即可以成为答案的备选项,需要先进队列再更新答案。
#### $Q8$:`if (hh <= tt && q[hh] < k - s * v) hh++;`
#### $Q_7$:`if (hh <= tt && q[hh] < k - s * v) hh++;`
不是应该是$0$~$s$个物品$i$吗,不应该是$(k-q[hh])/v>s+1$个项吗?
**答**:好问题!确实是$0$~$s$共$s+1$个,按理说单调队列长度最长应该是$s+1$,这里为什么只有$s$个长度呢?
@ -436,23 +249,23 @@ $f[i][j]$:前$i$个物品中选择,在体积上限是$j$的情况下,所
- 每一个二维表中的位置,都是可以从上一行中的某些位置转移而来的。比如:
$f[i-1][j] -> f[i][j]$
$f[i-1][j] \rightarrow f[i][j]$
$f[i-1][j-v]+w -> f[i][j]$
$f[i-1][j-v]+w \rightarrow f[i][j]$
$f[i-1][j-2v]+2w -> f[i][j]$
$f[i-1][j-2v]+2w \rightarrow f[i][j]$
$f[i-1][j-3v]+3w -> f[i][j]$
$f[i-1][j-3v]+3w \rightarrow f[i][j]$
....
$f[i-1][j-s*v]+s*w -> f[i][j]$
$f[i-1][j-s*v]+s*w \rightarrow f[i][j]$
当然,这也不一定都对,因为要保证$j-s*v>=0$
这些数据依赖是 **跳跃性的前序依赖**,所以,我们按对体积取模的余数分组,按组讨论,就可以把二维表填充满。
- 它的前序依赖单元格个数是$s$(指最大值)个,我们需要在这些个值中找出一个$max$。这是一个 距离我最近$X$个元素内找出最大值的典型问题:单调递减队列求区间最大值,队头元素即答案。
- 它的前序依赖单元格个数是$s$(指最大值)个,我们需要在这些个值中找出一个$max$。这是一个距离我最近$X$个元素内找出最大值的典型问题:单调递减队列求区间最大值,队头元素即答案。
- **$Q$:为什么是单调队列呢?如何运用单调队列求解呢?**
就是维护一个队列,它是由大到小的顺序单调存在的。对于后面每一个加入进来的数据,因为它是最新出生的,就算是最小,当前面老家伙们死光后,它也可能成为掌门人(黄鼠狼下豆鼠子,一辈不如一辈,这种情况就是可能的~),它必须保留!而它前面的老家伙,即使再厉害,由于年龄到了,也需要去世。没有来的及去世的老家伙们,因为能力值小于最后加入的数据,也就没有存在下去的必要,因为后面向前找,肯定先找到新出生而且能力值高的嘛,这些老家伙去世算了。

@ -7,23 +7,22 @@ const int M = 20010; // 背包容量上限
int n, m;
int f[N][M]; // 前i个物品在容量为j的限定下最大的价值总和
int q[M]; // 单调优化的队列
int q[M]; // 单调优化的队列,M是背包容量上限说明q[]里面保存的是体积
// 二维朴素版+队列[k-s*v,k],队列长s+1
// 二维+队列[k-s*v,k],队列长s+1
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) { // 考虑前i种物品
int v, w, s; // 体积、价值、个数
cin >> v >> w >> s;
// 其实下面的j,k是一体化的都是描述的剩余体积
// 之所以划分成两层循环是因为依赖的前序是按v为间隔的依赖并且是有个数限制的依赖
// j:按(对体积取模)分组
// 下面的j,k是一起用来描述剩余体积的,之所以划分成两层循环是因为依赖的前序是按v为间隔的依赖并且是有个数限制的依赖
// j:按对体积取模分组0表示剩余空间除以当前物品的体积余数是0
// k:分组内的每一个体积,注意:这里的体积不一定都是合法的,因为数量是有限制的
// 单调队列的意义查找前面k-s*v范围内的价值的最大值是一个单调递减的队列队头保存的是获取到最大值的最近体积
for (int j = 0; j < v; j++) {
int hh = 0, tt = -1;
for (int k = j; k <= m; k += v) {
for (int j = 0; j < v; j++) { // 按余数分组讨论
int hh = 0, tt = -1; // 全新的单调下降队列
for (int k = j; k <= m; k += v) { // 与j一起构成了有效体积
// 1、讨论到第i个物品时由于它最多只有s个所以有效的转移体积最小是k-s*v,更小的体积将被去除
if (hh <= tt && q[hh] < k - s * v) hh++;
// 2、处理队尾,下一个需要进入队列的是f[i-1][k],它是后来的,生命周期长,可以干死前面能力不如它的所有老头子,以保证一个单调递减的队列
@ -35,7 +34,6 @@ int main() {
}
}
}
printf("%d\n", f[n][m]);
return 0;
}

@ -9,7 +9,7 @@ int idx;
struct Node {
int v, w;
} c[N * 12];
} c[N * 31];
int main() {
cin >> n >> m;

@ -49,19 +49,13 @@ $1≤s_i≤1000$
该题就是一道 **混合背包** 的裸题
* 将$01$背包看成是数量只有$1$个的多重背包问题。
* 完全背包也不是真正的无限个数,因为受背包容量的限制,它最多可以使用的个数是$s_i=m/v_i$个,也就转化为多重背包问题。
* 使用多重背包问题的二进制优化统一处理即可。
* ① 将$01$背包看成是数量只有$1$个的多重背包问题
* ② 完全背包也不是真正的无限个数,因为受背包容量的限制,它最多可以使用的个数是$s_i=m/v_i$个,也就转化为多重背包问题
<font color='red' size=5><b>总结</b></font>
- $01$背包是多重背包的特殊形式;
使用多重背包问题的二进制优化统一处理即可
- 完全背包在背包容量限制下,也是多重背包的特殊形式
- 之所以它们各自有各自的状态转移方程,是因为特殊形式时的状态转移方程更简单,但本质上符合多重背包状态转移方程。
### 闫氏DP分析法
### 闫氏$ DP$分析法
<center><img src='https://cdn.acwing.com/media/article/image/2021/06/18/55909_0af30cf2cf-IMG_B5C67A3846AE-1.jpeg'></center>
### 一维数组解法 <font color='red' size=4><b>【推荐】</b></font>
@ -77,7 +71,7 @@ int idx;
struct Node {
int v, w;
} c[N * 12];
} c[N * 31];
int main() {
cin >> n >> m;
@ -111,4 +105,5 @@ int main() {
printf("%d\n", f[m]);
return 0;
}
```

@ -110,6 +110,72 @@ int main() {
- $01$背包,还是背一维的形式比较好,一来代码更短,二来空间更省,倒序就完了。
- 二维费用的$01$背包,简化版本的$01$背包模板就有了用武之地,因为三维数组可能会爆内存。
**[$AcWing$ $1020$. 潜水员](https://www.acwing.com/problem/content/description/1022/)**
二维费用背包问题,但是是一道 **反题**,与正常题目相左的一道题。
人家是装不下就不再装了,它的含义是相反的:我还缺少多少,如果你给的多,那么直接把我装满~
老套路,二维好想,一维好记:
**二维写法**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
const int M = 110;
int n, m, m1, m2;
int f[N][M][M];
int main() {
cin >> m1 >> m2 >> n;
memset(f, 0x3f, sizeof f);
f[0][0][0] = 0;
for (int i = 1; i <= n; i++) {
int v1, v2, w;
cin >> v1 >> v2 >> w;
for (int j = 0; j <= m1; j++)
for (int k = 0; k <= m2; k++) {
f[i][j][k] = f[i - 1][j][k];
f[i][j][k] = min(f[i - 1][j][k], f[i - 1][max(0, j - v1)][max(0, k - v2)] + w);
}
}
cout << f[n][m1][m2] << endl;
return 0;
}
```
**一维写法**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 22, M = 80;
int n, m, K;
int f[N][M];
int main() {
cin >> n >> m >> K;
memset(f, 0x3f, sizeof f);
f[0][0] = 0;
while (K--) {
int v1, v2, w;
cin >> v1 >> v2 >> w;
for (int i = n; i >= 0; i--)
for (int j = m; j >= 0; j--)
f[i][j] = min(f[i][j], f[max(0, i - v1)][max(0, j - v2)] + w);
}
cout << f[n][m] << endl;
return 0;
}
```
### 三、$01$背包之恰好装满
**[$AcWing$ $278$. 数字组合](https://www.acwing.com/problem/content/280/)**
@ -365,3 +431,191 @@ int main() {
```
### 六、多重背包
**[$AcWing$ $4$. 多重背包问题 I](https://www.acwing.com/problem/content/description/4/)**
**[$AcWing$ $1019$. 庆功会 ](https://www.acwing.com/problem/content/1021/)**
**数据范围**
$0<N,V100$
$0<v_i,w_i,s_i100$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N][N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) { // 讨论每个物品
int w, v, s;
cin >> v >> w >> s;
for (int j = 0; j <= m; j++) // 讨论每个剩余的体积
for (int k = 0; k <= s && v * k <= j; k++) // 讨论加入的个数
f[i][j] = max(f[i][j], f[i - 1][j - k * v] + w * k);
}
printf("%d\n", f[n][m]);
return 0;
}
```
**[$AcWing$ $5$. 多重背包问题 II](https://www.acwing.com/problem/content/description/5/)**
**数据范围**
$0<N1000$
$0<V2000$
$0<v_i,w_i,s_i2000$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010; // 个数上限
const int M = 2010; // 体积上限
int n, m, idx;
// 无法使用二维数组原因是因为分拆后N*31*M=31*1010*2010太大了MLE了
// 所以,需要使用滚动数组进行优化一下,思想还是二维的
int f[2][M];
struct Node {
int w, v;
} c[N * 31];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int v, w, s;
cin >> v >> w >> s;
for (int j = 1; j <= s; j *= 2) { // 1,2,4,8,16,32,64,128,...打包
c[++idx] = {j * w, j * v};
s -= j;
}
// 不够下一个2^n时独立成包
if (s) c[++idx] = {s * w, s * v};
}
// 按01背包跑就可以啦
for (int i = 1; i <= idx; i++)
for (int j = 1; j <= m; j++) {
f[i & 1][j] = f[i - 1 & 1][j];
if (j >= c[i].v)
f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j - c[i].v] + c[i].w);
}
// 输出
printf("%d\n", f[idx & 1][m]);
return 0;
}
```
**[$AcWing$ $6$. 多重背包问题 $III$](https://www.acwing.com/problem/content/6/)**
**数据范围**
$0<N1000$
$0<V20000$
$0<v_i,w_i,s_i20000$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010; // 物品种类上限
const int M = 20010; // 背包容量上限
int n, m;
int f[N][M]; // 前i个物品在容量为j的限定下最大的价值总和
int q[M]; // 单调优化的队列,M是背包容量上限说明q[]里面保存的是体积
// 二维+队列[k-s*v,k],队列长s+1
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) { // 考虑前i种物品
int v, w, s; // 体积、价值、个数
cin >> v >> w >> s;
// 下面的j,k是一起用来描述剩余体积的,之所以划分成两层循环是因为依赖的前序是按v为间隔的依赖并且是有个数限制的依赖
// j:按对体积取模分组0表示剩余空间除以当前物品的体积余数是0
// k:分组内的每一个体积,注意:这里的体积不一定都是合法的,因为数量是有限制的
// 单调队列的意义查找前面k-s*v范围内的价值的最大值是一个单调递减的队列队头保存的是获取到最大值的最近体积
for (int j = 0; j < v; j++) { //
int hh = 0, tt = -1; // 全新的单调下降队列
for (int k = j; k <= m; k += v) { // 与j一起构成了有效体积
// 1、讨论到第i个物品时由于它最多只有s个所以有效的转移体积最小是k-s*v,更小的体积将被去除
if (hh <= tt && q[hh] < k - s * v) hh++;
// 2、处理队尾,下一个需要进入队列的是f[i-1][k],它是后来的,生命周期长,可以干死前面能力不如它的所有老头子,以保证一个单调递减的队列
while (hh <= tt && f[i - 1][q[tt]] + (k - q[tt]) / v * w <= f[i - 1][k]) tt--;
// 3、k入队列
q[++tt] = k;
// 4、队列维护完毕f[i-1][k]已经进入队列,f[i][k]可以直接从队头取出区间最大值更新自己
f[i][k] = f[i - 1][q[hh]] + (k - q[hh]) / v * w;
}
}
}
printf("%d\n", f[n][m]);
return 0;
}
```
### 七、混合背包
* ① 将$01$背包看成是数量只有$1$个的多重背包问题
* ② 完全背包也不是真正的无限个数,因为受背包容量的限制,它最多可以使用的个数是$s_i=m/v_i$个,也就转化为多重背包问题
使用多重背包问题的二进制优化统一处理即可
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n; // 物品种类
int m; // 背包容量
int f[N]; // dp数组
int idx;
struct Node {
int v, w;
} c[N * 31];
int main() {
cin >> n >> m;
// 二进制打包
for (int i = 1; i <= n; i++) {
// 体积,价值,个数
int v, w, s;
cin >> v >> w >> s;
// 根据题意做一些小的变形
if (s == -1)
s = 1; // 题目中s=-1表示只有1个
else if (s == 0)
s = m / v; // 完全背包(其实本质上就是多重背包):最多总体积/该物品体积向下取整
// 如果是其它大于0的数字那么是多重背包
// 将完全背包和多重背包利用二进制优化转化为01背包
for (int j = 1; j <= s; j *= 2) {
c[++idx] = {j * v, j * w};
s -= j;
}
// 不够下一个2^n时独立成包
if (s) c[++idx] = {s * v, s * w};
}
// 01背包
for (int i = 1; i <= idx; i++)
for (int j = m; j >= c[i].v; j--)
f[j] = max(f[j], f[j - c[i].v] + c[i].w);
// 输出
printf("%d\n", f[m]);
return 0;
}
```

@ -0,0 +1 @@
<mxfile host="Electron" modified="2024-03-11T07:49:39.890Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.9.6 Chrome/89.0.4389.128 Electron/12.0.16 Safari/537.36" etag="tFvaNrIXzMi-P0cMovxI" version="14.9.6" type="device"><diagram id="4J5xsyxzfobqGXuH9jze" name="第 1 页">7Ztbb5swFIB/DY+TwOaWxyRtt5dJ0/rQvU0ouMEdwRE4De2vn2lsCDYRqRUn2nDVKOFgjsP5fDgXiAOXm/prmWyz7yRFuQPctHbgnQOA5wPgNP9u+naQxJAL1iVO+aBO8IjfERe6XLrDKap6AykhOcXbvnBFigKtaE+WlCXZ94c9k7w/6zZZI0XwuEpyVfqEU5pxqRfOuh3fEF5nfOoYRIcdm0QM5mdSZUlK9kcieO/AZUkIPXza1EuUN8YTdjkc93Bib/vFSlTQcw6g7yTbbX4+/UrrGgFwV+OX31+4ltck33Un7DYiyF8cV0XfhEkoqtmEi4xucibw2MeKluQPWpKclExSkIKNXDzjPJdESY7XBdtcsa+MmHzxikqKmbHnfMcGp2kzzWKfYYoet8mqmXPPlhaTlWRXpKg5G7dRTwrKl4sfsG1+Ikwhqk9ayGvtzhYsIhtEyzc2hB/gc1J8qYZ8c99xb5dldoRcAE74Ulu3ijsa7AMH8gk4QIGjsEBFOm9WeWPUPKkqvPrAkZRUFR8BGzYeShVfGDXdkXGCAdsIWYnyhOLXvvohg/EZfhDMJm7JhH0ynmzyiuzKFeIHHfuApGc2oocZbo2ooueDXnvS+kChBcpBACiR8DWJeu6IIsNIA4tUkBhzrrORymvjyl4aKkhFPBTv3mRj4u2DYmQdbjgoQm1/80cUGfa32BIVJGKJxKWioqLIMNKZRXoi0dFGCm6MVFzkz2TKo1uaVNlHePL+bYyKQ4WX8kz3yhjVin9KGOHsQhhlRdfGqPYGRHLqs1fgTLlx40e3TlI93wbAEwEwCDRdLhzRY9rjbKXfXvy8/sUvgJpIZyN6TCNVK/3JIg0k74KeHlK5eaMoMs3U9ge6ajKWUABNpuGIItNM1Q6B6Mb5/D1QIE8l2Qlvn+zYav9UAyfSTXbkMlFRZNjlxGKxTEEc9EnoJjvhiB7TRD9X+P/PROVkJ7pUsqMoMs1U7QJMlSnzUwmFZrID3BFFppmqDwkMJDtTuw0ZRFLWoyY9EF4z6QG2HdAGSK/PRvtZjhCMKDLterZ2FCiisWcwzmY69uiWaab27nLLVPZTT5ep/KSPrMg0U1twtkyjfgtV20+jYESR6UcjbcHZFZyhhEKzPInhiCLTTNV4Cp370Jkvnfk5uavJnLTZFimwA+DDg8v+LpOrQql3M1NzVW+oQSdnOxfLVaEaA/0pcJDiHYhvDkINXJMAITnE0P3564Lw1WgzRRBD9w6uDELtSgYTADGTLk2BOQ5ss/uN2SGyd7/Ug/d/AQ==</diagram></mxfile>

@ -0,0 +1,16 @@
5
1 2 3 3 2
5
2 3 2 3 1
5
2 3 4 5 2
5
3 2 4 2 5
7
3 2 4 2 5 3 1
0

@ -1,30 +1,50 @@
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int a[N];
int st[10];
vector<PII> q;
int res;
int n;
/*
5
12321
int main() {
#ifndef ONLINE_JUDGE
freopen("6.in", "r", stdin);
#endif
*/
int cnt = 1;
while (cin >> n && n) {
// 各种清空
memset(a, 0, sizeof a);
memset(st, 0, sizeof st);
q.clear();
res = 0;
// 递归写法
void dfs(string s) {
int p;
for (p = s.size() - 1; p >= 1; p--)
if (s[0] != s[p]) break;
for (int i = 1; i <= n; i++) cin >> a[i]; // 密码数组
for (int i = 1; i <= n; i++) { // 遍历每个位置
if (!st[a[i]]) { // 如果此数字没有被处理过
st[a[i]] = 1; // 标识已处理
for (int j = n; j; j--) // 从后向前找到它的终止位置
if (a[i] == a[j]) {
q.push_back({i, j}); // 记录有效区间
break;
}
}
}
string t;
for (int i = 1; i <= p; i++) t += s[i];
if (t.size()) cnt++, dfs(t);
}
// 从左到右枚举,所以,不再需再进行按左端点排序,现在就是按左端点排序完的
// Q:求一组区间的相交区间个数?如果存在一个相交区间就多加1个1
res = q.size();
int main() {
int n;
string s;
cin >> n >> s;
dfs(s);
cout << cnt << endl;
// 如果存在区间相交则res++
for (int i = 0; i < q.size(); i++)
for (int j = i + 1; j < q.size(); j++)
if (q[i].second > q[j].first && q[j].second > q[i].second) res++;
for (int i = 1; i <= n; i++) cout << a[i] << " ";
cout << endl;
cout << res << endl;
cout << endl;
}
return 0;
}

@ -0,0 +1,64 @@
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
/*
4
1 2 3 4
*/
const int N = 110;
struct Node {
int num; // 啥数
int left, right; // 左侧是哪个节点,右侧是哪个节点
} a[N];
int st[N];
int n;
LL res;
string finalPath;
void dfs(int r, LL sum, string path) { // u:本轮选择哪个靶子, r:剩余几次机会 sum:已经取得的累加和
if (r == 0) {
if (sum > res) {
res = sum;
finalPath = path;
}
return;
}
for (int u = 1; u <= n; u++) {
if (!st[u]) {
int L = a[u].left, R = a[u].right;
st[u] = 1; // u号靶子被标识为已放倒
a[L].right = R;
a[R].left = L;
// 深搜
dfs(r - 1, sum + a[L].num * a[u].num * a[R].num, path + to_string(u));
// 回溯
a[L].right = u;
a[R].left = u;
st[u] = 0; // u被扶了起来
}
}
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].num;
// 左右哨兵
a[0].num = 1;
a[n + 1].num = 1;
// 预处理
for (int i = 1; i <= n; i++) {
a[i].left = i - 1;
a[i].right = i + 1;
}
dfs(n, 0, "");
cout << res << endl;
cout << finalPath << endl;
return 0;
}

@ -0,0 +1,6 @@
https://gesp.ccf.org.cn/
18686619970
mdcija780522
进入后台
Loading…
Cancel
Save