动态规划问题之背包模型(18题)

背包问题是动态规划问题的一大类型,下面我们对这个进行总结。
以 Acwing y中总结的 几个类型,我写了几个题解

应用知识点

  • 01背包、完全背包 空间压缩的写法
  • 多维费用的背包问题,以及状态的不同表示对复杂度的 影响
  • 完全背包问题的三种求解方法O(NMS),O(NMlogS),O(NM)O(NMS), O(NMlogS),O(NM)O(NMS),O(NMlogS),O(NM)
  • dp维度关系等于,小于等于,大于等于对初始化的影响(潜水员)
  • dp关键信息最小值,最大值,方案数对初始化的影响

一、采药

Acwing 题目链接
非常裸的 01 背包
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;const int N = 1010;
int f[N][N];
int n, m;int main() 
{cin >> m >> n;memset(f, 0, sizeof f);for (int i = 1; i <= n; i ++ ) {static int t, v;scanf("%d%d", &t, &v);for (int j = 0; j <= m; j ++ ) {f[i][j] = f[i - 1][j];if (j >= t) {f[i][j] = max(f[i][j], f[i - 1][j - t] + v);}}}cout << f[n][m] << endl;return 0;
}

二、装箱问题

ACwing 题目链接
在这里插入图片描述
对于该问题,我给出两个解法(其实都类似 )


第一种
f[i][j] 表示利用前 i 个箱子,体积恰好为 j 的方案是否可行,是一个 bool 数组
那么 f[i][j]=f[i−1][j]∣f[i−1][j−v[i]]f[i][j] = f[i-1][j] | f[i-1][j - v[i]]f[i][j]=f[i1][j]f[i1][jv[i]]
初始化时, f[0][0]f[0][0]f[0][0] 置为 true,其余置为 false
下面是进行空间优化的写法

#include <bits/stdc++.h>
using namespace std;const int N = 40, M = 40010;
int f[M];
int n, m;
int cost[N];
// f[i][j] 表示前 i个物品,体积 恰好等于 j 是否可以
// 然后进行状态压缩
int main()
{// inputcin >> m >> n;for (int i = 1; i <= n; i ++ )scanf("%d", &cost[i]);f[0] = true;for (int i = 1; i <= n; i ++ ) {for (int j = m; j >= cost[i]; j -- ) {f[j] |= f[j - cost[i]];}}for (int i = m; ; i -- ) {if (f[i]) {cout << m - i << endl;break;}}return 0;
}


第二种写法
f[i][j]f[i][j]f[i][j]表示的是对前 i 个箱子,体积小于等于 j 时候的最大体积
f[i][j]=max(f[i−1][j],f[i−1][j−v[i]]+v[i]f[i][j] =max(f[i-1][j], f[i-1][j-v[i]] + v[i]f[i][j]=max(f[i1][j],f[i1][jv[i]]+v[i]
这样的话,初始化全部为 0
而且最后输出结果也比较简单
m−f[n][m]m - f[n][m]mf[n][m]
下面给出空间优化之后的代码

#include <bits/stdc++.h>
using namespace std;const int N = 40010;
int f[N];
int n, m;
// f[i][j] 表示前 i 个物品,体积小于等于 j 时候可以容纳的最大体积
// 然后进行状态压缩int main()
{cin>>m>>n;int v;memset(f, 0, sizeof f);for(int i = 1; i <= n; i++){cin>>v;for(int j = m; j >= v; j--){f[j] = max(f[j], f[j-v]+v);}}cout<<m-f[m]<<endl;return 0;
}

三、宠物小精灵值收服

Acwing 题目链接
在这里插入图片描述
在这里插入图片描述
本题是一个二维费用的 01 背包,理解起来不拿,不过题目属实有点长,而且 皮卡丘 的血量为 0 也会抓取失败,这是一个比较坑的点
下面,我给出两个dp的解决方案


方案一
f[i][j][k]f[i][j][k]f[i][j][k] 表示对前 i 个怪兽进行遍历,消耗精灵球数量小于等于j,消耗血量小于等于 k,的最大抓捕量
f[i][j][k]=max(f[i−1][k][k],f[i−1][j−cost1i][k−cost2i])f[i][j][k] = max(f[i-1][k][k], f[i-1][j-cost1_i][k-cost2_i])f[i][j][k]=max(f[i1][k][k],f[i1][jcost1i][kcost2i])
对应的代码如下

#include <bits/stdc++.h>
using namespace std;const int N = 1010, M = 510, K = 110, INF = 0x3f3f3f3f;
int cost1[K], cost2[K], n, m, k;
int f[N][M];int main()
{// inputcin >> n >> m >> k;m --;for (int i = 1; i <= k; i ++ ) {scanf("%d%d", &cost1[i], &cost2[i]);}for (int i = 1; i <= k; i ++ ) {for (int j = n; j >= cost1[i]; j -- ) {for (int k = m; k >= cost2[i]; k -- ) {f[j][k] = max(f[j][k], f[j - cost1[i]][k - cost2[i]] + 1);}}}int ans1, ans2;ans1 = f[n][m];for (int j = m; ; j -- ) {if (ans1 == 0) {ans2 = 0;break;}if (f[n][j] == ans1) {ans2 = j;} else {break;}}printf("%d %d\n", ans1, m - ans2 + 1);return 0;
}

但是我们将复杂度考虑进去的话,O(KNM)O(KNM)O(KNM)有时候会过大,万一被卡怎么办? 给出方案二

方案二
f[i][M][K]f[i][M][K]f[i][M][K] 遍历前面i个精灵,统计的是在 体力恰好为 m, 捕捉恰好为 k 时候的最小消耗求的数量
复杂度O(KKM)O(KKM)O(KKM)快了那么一点点
f[i][[j][k]=min(f[i−1][j][k],f[i−1][m−cost2i][k−1]+cost1i)f[i][[j][k]=min(f[i-1][j][k], f[i-1][m-cost2_i][k-1]+cost1_i)f[i][[j][k]=min(f[i1][j][k],f[i1][mcost2i][k1]+cost1i)
代码如下

#include <bits/stdc++.h>
using namespace std;const int N = 1010, M = 510, K = 110, INF = 0x3f3f3f3f;
int n, m, k1;
int cost1[K], cost2[N];
int f[M][K];    // 统计的是在 体力恰好为 m, 捕捉恰好为 k 时候的最小消耗求的数量int main() 
{// inputcin >> n >> m >> k1;for (int i = 1; i <= k1; i ++ ) {scanf("%d%d", &cost1[i], &cost2[i]);}memset(f, 0x3f, sizeof f); f[0][0] = 0;for (int i = 1; i <= k1; i ++ ) {for (int j = m - 1; j >= cost2[i]; j -- ) {for (int k = k1; k >= 1; k -- ) {f[j][k] = min(f[j][k], f[j - cost2[i]][k - 1] + cost1[i]);}}}int ans1 = 0, ans2 = 0;for (int j = 0; j <= m - 1; j ++ ) {for (int k = 0; k < k1; k ++) {if (f[j][k] <= n) {if (k > ans1) {ans1 = k, ans2 = j;} else if (f[j][k] == n && j < ans2) {ans2 = j;}}}}printf("%d %d\n", ans1, m - ans2);return 0;
}

DP很灵活,有时候换一个dp思路,就可以优化一下复杂度

四、数字组合

Acwing 题目链接
在这里插入图片描述
挺简单一个 dp问题,和 01 背包很像
下面给出空间优化的代码

#include <bits/stdc++.h>
using namespace std;const int N = 10010;
int f[N], a[N];
int n, m;int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) {scanf("%d", &a[i]);}memset(f, 0, sizeof f);f[0] = 1;for (int i = 1; i <= n; i ++ ) {for (int j = m; j >= a[i]; j -- ) {f[j] += f[j - a[i]];}}cout << f[m] << endl;return 0;
}

五、买书

题目链接

在这里插入图片描述

挺简单的一个 完全背包问题
将书的个数看成 value,花费就是费用,然后跑一个完全背包

#include <bits/stdc++.h>
using namespace std;typedef long long LL;
typedef pair<int, int> PII;
const int M = 1010;
LL f[M];
int n = 4, m;
int a[6] = {0, 10, 20, 50, 100};int main()
{cin >> m;memset(f, 0, sizeof f);f[0] = 1;for (int i = 1; i <= 4; i ++ ) {for (int j = a[i]; j <= m; j ++ ) {f[j] += f[j - a[i]];}}cout << f[m] << endl;return 0;
}

六、货币系统

Problem Link
在这里插入图片描述
在这里插入图片描述
题目大意就是 在 n 个数 a1,a2,…,ana_1,a_2,\dots,a_na1,a2,,an中,尽可能的选取少的数字将原数组给表示出来
解题思路如下

  • 首先,我们先将 原数组 a 从小到大进行排序
  • 最小的数字是不可以被表示的,因此最小的数字 a1a_1a1需要被选出,放在bbb数组中
  • 然后我们查看 b 数组可以组合出哪些数字
  • 不断对 a 数组的元素进行遍历,倘若他无法被 b 表示,那么他需要加入到 b 中,这是因为 如果 a[i]a[i]a[i] 无法被 a[0..i]a[0..i]a[0..i]所表示,那么他就无法被表示,需要放入支撑集 b 中

这就是一个完全背包的模型

/*这次的dp需要考虑到最小依赖集这个东西:对于 (n, a) ==> (m, b):那么:1. a1、、an一定是可以被b表示出来的2. b 一定是属于 a, 主要是因为倘若 b 不属于 a, 那么 b = sum(a[i] * t[i]) = sum(b[i] * t[i])自己被自己表示3. b不能被自己表示因此这个问题转换为了 求 不能被自己组成的a,然后作为b的一部分,也就是一个完全背包题目;代码简单,但是思路有点意思
*/
#include <bits/stdc++.h>
using namespace std;const int M = 25010, N = 110;
int a[N], n, m;
bool f[M];void sol() {sort(a + 1, a + 1 + n);m = a[n];memset(f, 0, sizeof f);f[0] = true;int res = 0;for (int i = 1; i <= n; i ++ ) {if (f[a[i]]) {continue;}// 无法被表示,需要被加入res ++;for (int j = a[i]; j <= m; j ++ ) {f[j] |= f[j - a[i]];}}printf("%d\n", res);}int main() 
{int T;  cin >> T;while (T -- ) {scanf("%d", &n);for (int i = 1; i <= n; i ++ ) {scanf("%d", &a[i]);}sol();}
}

七、多重背包问题III

多重背包问题链接
在这里插入图片描述

普通多重背包问题朴素写法O(NMS)O(NMS)O(NMS)
f(i,j)f(i, j)f(i,j)表示前i个物品,容量小于等于jjj的最大价值

#include <bits/stdc++.h>
using namespace std;const int N = 1010;
int f[N][N];
int n, m, v[N], w[N], s[N];int main()
{memset(f, 0, sizeof f);cin >> n >> m;for (int i = 1; i <= n; i ++ )  scanf("%d%d%d", &v[i], &w[i], &s[i]);for (int i = 1; i <= n; i ++ ) {for (int j = 0; j <= m; j ++ ) {for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ ) {f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);}}}cout << f[n][m] << endl;return 0;
}

多重背包问题二进制优化方法O(NMlogS)O(NMlogS)O(NMlogS)
首先,对于容量,价值,和数量分别为 vi,wi,siv_i,w_i,s_ivi,wi,si的物品iii,我们可以将它分解为
多个 01 背包的物品
倘若 si=10s_i = 10si=10,我们可以将其分为
    (vi,wi),(2vi,2wi),(4vi,4wi),(3vi,3wi)(v_i,w_i),(2v_i,2w_i),(4v_i,4w_i),(3v_i,3w_i)(vi,wi),(2vi,2wi),(4vi,4wi),(3vi,3wi),这几个 01背包,可以将
    (vi,wi),(2vi,2wi),(3vi,3wi),…(siwi,sivi)(v_i,w_i),(2v_i,2w_i),(3v_i,3w_i), \dots (s_iw_i,s_i v_i)(vi,wi),(2vi,2wi),(3vi,3wi),(siwi,sivi)全部给枚举一遍

这个是可以证明的
    (w,v),(2w,2v),(4w,4v),(2kw,2kv),(surplus∗w,surplus∗v),其中surplus≥2k,1+2+4+⋯+2k+surplus=s(w,v),(2w,2v),(4w,4v),(2^kw,2^kv),(surplus*w,surplus*v), 其中surplus\geq 2^k,1+2+4+\dots+2^k+surplus=s(w,v),(2w,2v),(4w,4v),(2kw,2kv),(surplusw,surplusv),surplus2k,1+2+4++2k+surplus=s

    首先1,2,4,⋯,2k1,2,4,\cdots,2^k1,2,4,,2k可以将[0,2k+1][0,2^k+1][0,2k+1]完全包含,通过surplussurplussurplus区间再次移动,将[0,s][0,s][0,s]方案完全覆盖掉。
对用代码如下

#include <bits/stdc++.h>
using namespace std;const int N = 2010;
int f[N];
int n, m, v[N * 20], w[N * 20];int main()
{memset(f, 0, sizeof f);cin >> n >> m;int n2 = 0;// 多重背包拆成 01 背包for (int i = 1; i <= n; i ++ )  {static int a, b, s;scanf("%d%d%d", &a, &b, &s);int base = 1;while (true) {if (s <= base) {v[++ n2] = s * a;w[n2] = s * b;break;} else {s -= base;v[++ n2] = base * a;w[n2] = base * b;base *= 2;}}}// 跑一遍 01 背包n = n2;for (int i = 1; i <= n; i ++ ) {for (int j = m; j >= v[i]; j -- ) {// f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);f[j] = max(f[j], f[j -  v[i]] + w[i]);}}cout << f[m] << endl;return 0;
}

完全背包的优化,类似于前缀最大值,
然后我们多重背包的优化,利用单调队列的优化


下面我来介绍优化的方法


首先,我们还是先对 i 个物品进行遍历
for(int i=1;i <= n; i ++ )
此时,我们第 i 个物品的体积为 v,价值为w,可用数量为 j
考虑一下我们的状态转移方程f[i][j]=max(f[i−1][j],f[i−1][j−v]+w,f[i−1][j−2v]+2w…,f[i−1][j−sv]+sw)f[i][j] = max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w\dots ,f[i-1][j-sv]+sw)f[i][j]=max(f[i1][j],f[i1][jv]+w,f[i1][j2v]+2w,f[i1][jsv]+sw),发现,倘若我们将原本的 f[i−1][j]f[i-1][j]f[i1][j] 分组,组别如下

组号内容
0f[i][0], f[i][v], f[i][2v],…,f[i][kb+1]
1f[i][1], f[i][v+1], f[i][2v+1],…,f[i][kv+1]
…\dots⋯⋯\cdots \cdots
v-1f[i][v-1], f[i][v+v-1], f[i][2v+v-1],…,f[i][kv+v-1]

不难发现,只有组内之间还是有递归关系的,组间不存在联系,下面我们讨论组内是如何优化的的

首先,我们假设当前更新的 f[i][0…m] 为数组 dp[M], 上一层的 f[i-1][0…m]为数组pre[M]
有以下公式

dp[j] = pre[j]
dp[j+v] = max(pre[j] + w, pre[j+v])
dp[j+2v] = max(pre[j] + 2w, pre[j+v] + w, pre[j+2v])
dp[j+3v] = max(pre[j] + 3w, pre[j+v] + 2w, pre[j+2v] + w, pre[j+3v])

进一步整理一下
dp[j] = pre[j]
dp[j+v] = max(pre[j], pre[j+v] - w) + w
dp[j+2v] = max(pre[j], pre[j+v] - w, pre[j+2v] - 2w) + 2w
dp[j+3v] = max(pre[j], pre[j+v] - w, pre[j+2v] - 2w, pre[j+3v] - 3w) + 3w

不难发现,经过这样的整理,我们的 dp 数组就是在原数组基础之上增加了一个 滑动窗口求最大值的算法(单调队列,应该是递减的)

所以说,对应的代码如下所示

#include <bits/stdc++.h>
using namespace std;const int N = 1010, M = 20010;
int f[M], pre[M], n, m;
int v, w, s;
int que[M];int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) {    // 对每个物品进行遍历scanf("%d%d%d", &v, &w, &s);memcpy(pre, f, sizeof f);   // 将 i - 1 层的状态保存起来,后面dp需要使用for (int j = 0; j < v; j ++ ) { // 对每一类进行遍历,j, j+v, j+2v,..static int head, tail;head = 0, tail = -1;    // 循环队列for (int k = j; k <= m; k += v) {   // 对该类进行 动态规划// 首先更新我们的滑动窗口,去掉队列前划过去的,往后面加入if (head <= tail && k - que[head] > s * v) {    // 队列头超过了窗口的最左侧head ++;}while (head <= tail && (pre[que[tail]] - (que[tail] - j) / v * w) <= (pre[k] - (k - j) / v * w)) {  // 用于保证窗口的单调性-- tail;}que[++ tail] = k;   // 增加窗口的尾部f[k] = pre[que[head]] + (k - que[head]) / v * w;  // 用窗口的维护数值来更新结果}}}cout << f[m] << endl;return 0;
}

八、庆功会

Acwing 链接
在这里插入图片描述
本题就是一个很裸的 多重背包问题
通过观察复杂度,不难发现, NMS 复杂度就可以过去
倘若空间不够存储的话,可以采取滚动数组进行优化

#include <bits/stdc++.h>
using namespace std;const int N = 510, M = 6010;
int f[N][M];
int v, w, s, n, m;int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) {scanf("%d%d%d", &v, &w, &s);for (int j = 0; j <= m; j ++ ) {    // f[i][j] = max(f[i-1][j], f[i-1][j-kv] + kw)f[i][j] = f[i - 1][j];for (int k = 1; k <= s; k ++ ) {if (j < k * v)  break;f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);}}}cout << f[n][m] << endl;return 0;
}

空间优化之后的代码

#include <bits/stdc++.h>
using namespace std;const int N = 510, M = 6010;
int f[M];
int v, w, s, n, m;int main()
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) {scanf("%d%d%d", &v, &w, &s);for (int j = m; j >= 0; j -- ) {    // 注意这个顺序// f[j] = f[j];// f[i][j] = max(f[i-1][j], f[i-1][j-kv] + kw)for (int k = 1; k <= s; k ++ ) {if (j < k * v)  break;f[j] = max(f[j], f[j - k * v] + k * w);}}}cout << f[m] << endl;return 0;
}

九、混合背包问题

Acwing 混合背包
在这里插入图片描述
这就是一个 01 背包,完全背包,多重背包的综合
这里多重背包使用 二进制优化 即可跑完,复杂度O(NMlogS)O(NMlogS)O(NMlogS),时间复杂度没有超时
下面给出 空间优化的解题方法

#include <bits/stdc++.h>
using namespace std;const int M = 1010;
int f[M], n, m, v, w, s;int main()
{cin >> n >> m;memset(f, 0, sizeof f);for (int i = 1; i <= n; i ++ ) {    scanf("%d%d%d", &v, &w, &s);if (s == 0) {// 完全背包for (int j = v; j <= m; j ++ ) {f[j] = max(f[j], f[j - v] + w);}} else {    // 01 背包和多重背包if (s == -1) {s = 1;}for (int k = 0; (1 << k) <= s; k ++ ) {s -= (1 << k);for (int j = m; j >= v * (1 << k); j -- ) {   // v * (1 << k)f[j] = max(f[j], f[j - v * (1 << k)] + w * (1 << k));}}if (s) {for (int j = m; j >= s * v; j -- ) {    // 注意这个下界的判断f[j] = max(f[j], f[j - s * v] + s * w);}}}}cout << f[m] << endl;return 0;
}

十、二维费用的背包问题

Acwing 题目链接
在这里插入图片描述
一个非常裸的 二维费用 背包问题

#include <bits/stdc++.h>
using namespace std;const int N = 110;
int f[N][N];
int n, v, m;int main()
{memset(f, 0, sizeof f); // 01 bagcin >> n >> v >> m;for (int i = 1; i <= n; i ++ ) {static int a, b, c;scanf("%d%d%d", &a, &b, &c);for (int j = v; j >= a; j -- ) {for (int k = m; k >= b; k -- ) {f[j][k] = max(f[j][k], f[j - a][k - b] + c);}}}cout << f[v][m] << endl;return 0;
}

十一、潜水员

Acwing 题目链接

在这里插入图片描述
在这里插入图片描述

首先,我先给出一个错误的方法,然后说为什么是不可以的
该种方法 f[i][j][k]f[i][j][k]f[i][j][k] 表示对前 iii 个物品,选取氧气量恰好为 jjj,氮气量恰好为kkk,他所对应的最小重量
初始化,显然 memset(f, 0, sizeof f), f[0][0][0] = 1
对应的代码如下所示

#include <bits/stdc++.h>
using namespace std;const int M = 30, N = 110, INF = 0x3f3f3f3f;
int f[M * 2 + 5][N * 2 + 5];
int m, n, k1;int main()
{cin >> m >> n;cin >> k1;memset(f, 0x3f, sizeof f);f[0][0] = 0;for (int i = 1; i <= k1; i ++ ) {    static int a, b, c;scanf("%d%d%d", &a, &b, &c);// 一定要注意他这个超限制的情况!// 这样做也错是因为,虽然 m 已经够了,但是因为 n还不够,因此 m 的增加量可以使很大的for (int j = m + M; j >= a; j -- ) {for (int k = n + N; k >= b; k -- ) {f[j][k] = min(f[j][k], f[j - a][k - b] + c);}   }}int ans = INF;for (int j = m; j <= m + M; j ++ ) {for (int k = n; k <= n + N; k ++ ) {ans = min(ans, f[j][k]);}}cout << ans << endl;return 0;
}

但是该种方案是错误的!


    原因在于,我们寻找的方案是 氧气容量 大于等于 m, 氮气容量大于等于 n 的最消息重量的解决方案,**但是**上限是多少,我们是没有办法确定了     你可以理所当然的以为 上限是 m * 2, n * 2, 或者是 m + M, n + N,但是都不对     上限其实是 N * M 级别的,因为倘若氧气很充足,但是氮气就是很少,因为氮气不足需要不断加入气缸使得氮气达到标准,与此同时氧气也在增加,最坏情况下是 M * N     因此,数组大小很爆炸,消耗时间也很爆炸,虽然递归式子没问题,但是从时间和空间的角度来看,并不可行。

下面我们考虑另一种dp思路

f[i][j][k] 表示的是 前 i 个物品,氧气量大于等于 j,氮气量大于等于 k 的最小重量,同样是从初始化和状态转移方程两个方面进行分析

初始化方面
f[0][x][y]=INF, 如果x>0∣∣y>0x>0 || y>0x>0y>0
f[0][x][y]=0,如果x<=0&&y<=0x<=0\&\&y<=0x<=0&&y<=0
其他都初始化为 INF
但是,因为我们的数组下表数值都为正数,也就是说取不到这个负数的情况,因此后面的运算对于负值的下标我们是通过特判来选取数值的。

状态转移方程
对于遍历第 i 个物品的属性 a, b, c
f[i][j][k]=max(f[i-1][j][k], f[i-1][j-a][k-b]+v)
这个方程是很好理解的,但是关键是我们有的合法状态是小于等于 0 的情况,使用数组是没有办法表示的(即使使用偏移量,也因为空间过大,而不可行,类似于我们讨论的第一种方法)

那么,我们是如何解决这个问题的呢?
在这里插入图片描述

对应代码如下所示

/*f[i][j]:表示恰到达到 i, j这个的时候, 最小重量注意是最小,初始化应该为INF它需要的是M, 但是我们不能遍历只是到M, N注意,如果是恰好的话,那么,范围太大, 数组开不了,而且会超时但是这个数据开的 应该要很大,很烦, 应该是 [N*M][N*M]....因此我们应该把这个状态给改了,不适用这个f[i][j]:表示大于等于 i, j这个的时候, 最小重量f[-x][-y] = 0;others INF;
*/
#include <bits/stdc++.h>
using namespace std;const int M = 30, N = 110, INF = 0x3f3f3f3f;
int f[M * 2 + 5][N * 2 + 5];
int m, n, k1;int main()
{cin >> m >> n;cin >> k1;memset(f, 0x3f, sizeof f);f[0][0] = 0;for (int i = 1; i <= k1; i ++ ) {    static int a, b, c;scanf("%d%d%d", &a, &b, &c);for (int j = m; j >= 0; j -- ) {for (int k = n; k >= 0; k -- ) {if (j - a >= 0 && k - b >= 0) {f[j][k] = min(f[j][k], f[j - a][k - b] + c);} else if (j - a < 0 && k - b < 0) {f[j][k] = min(0 + c, f[j][k]);} else {f[j][k] = min(f[j][k], f[max(0, j - a)][max(0, k - b)] + c);}}   }}cout << f[m][n] << endl;return 0;
}

十二、机器分配

ACWing Problem Link
在这里插入图片描述
挺简单个一个题目,该问题非 01 背包,也非完全背包、多重背包,因为虽然是设备分配给同一个公司、或者是其他公司,增加的价值是不一样的!

它更像是一个分组背包,f[i][j] i 表示的第 i 组,选取组内的物品,组内的物品可以使一个设备、两个设备、⋯\cdots 多个设备

f[i][j]f[i][j]f[i][j] 可以表示为前 i 个公司分配小于等于 j 个设备时候的最大价值
f[i][j]=maxf[i−1][j],f[i−1][j−1]+w[i][1],f[i−1][j−2]+w[i][2],⋅f[i−1][j−j]+w[i][j]f[i][j] = max{f[i-1][j], f[i-1][j-1]+w[i][1], f[i-1][j-2]+w[i][2], \cdot f[i-1][j-j]+w[i][j]}f[i][j]=maxf[i1][j],f[i1][j1]+w[i][1],f[i1][j2]+w[i][2],f[i1][jj]+w[i][j]
但是,因为最后需要输出分配的方案,我们需要数组 g[i][j] 记录一下到达 f[i][j] 这个状态时候,公司 i 分配了多少台机器,之后就可以知道合法方案了

代码如下

#include <bits/stdc++.h>
using namespace std;const int N = 15, M = 20;
int f[N][M], w[N][M];
int g[N][M], n, m;int main() 
{cin >> n >> m;for (int i = 1; i <= n; i ++ ) {for (int j = 1; j <= m; j ++ ) {scanf("%d", &w[i][j]);}}memset(f, 0, sizeof f);memset(g, 0, sizeof g);for (int i = 1; i <= n; i ++ ) {for (int j = 1; j <= m; j ++ ) {for (int k = 0; k <= j; k ++ ) {if (f[i][j] < f[i - 1][j - k] + w[i][k]) {f[i][j] = f[i - 1][j - k] + w[i][k];g[i][j] = k;}}}}// find the maxint ri, rj, res = -1;for (int j = 0; j <= m; j ++ ) {if (f[n][j] > res) {res = f[n][j];ri = n, rj = j;}}// find the pathvector<int> path;while (ri != 0) {path.push_back(g[ri][rj]);rj -= g[ri][rj];ri --;}reverse(path.begin(), path.end());cout << res << endl;for (int i = 0; i < path.size(); i ++ ) {printf("%d %d\n", i + 1, path[i]);}return 0;
}

十三、开心的金明

ACWing 题目链接
在这里插入图片描述
在这里插入图片描述
写一个空间优化的 01 背包即可

#include <bits/stdc++.h>
using namespace std;const int N = 30, M = 30010;
int f[M], n, m;int main()
{cin >> m >> n;memset(f, 0, sizeof f);for (int i = 1; i <= n; i ++ ) {static int level, v;scanf("%d%d", &v, &level);for (int j = m; j >= v; j -- ) {f[j] = max(f[j], f[j - v] + v * level);}}cout << f[m] << endl;return 0;
}

十四、有依赖的背包问题

ACWing Link在这里插入图片描述
在这里插入图片描述
一个树状的有依赖背包问题,
f[u][j] 表示的是 u 作为根的子树,当需要的体积小于等于 j 的最大价值
显然f[u][j] 的更新是依赖于他的儿子的,(因为是树,我们必须是dfs来确定便利的先后顺序,而不是之间 for i = 1;i <= n; i ++了)
首先,我们先让 父节点为 u 的点集合 set 跑一个 分组背包,得到 没有加入 u 的 f[u][j],之后我们在人为的加入 u 这个点就可以了,具体还可以优化,请看下面的代码.

#include <bits/stdc++.h>
using namespace std;const int N = 110, INF = 0x3f3f3f3f;int f[N][N], n, m, v[N], w[N];
bool st[N];
int h[N], e[N], ne[N], idx;void add(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}void dfs(int u, int m) {int v2;for (int i = h[u]; ~i; i = ne[i]) {v2 = e[i];if (!st[v2]) {dfs(v2, m - v[u]);   // 减小一下子复杂度// dfs(v2, m);st[v2] = true;}// 多重背包for (int j = m; j >= 0; j -- ) {    // 注意,这里是大于等于 0,和 Vi 没有关系,而且注意这个优化空间的顺序for (int k = 0; k <= j; k ++ ) {f[u][j] = max(f[u][j], f[v2][k] + f[u][j - k]);}   }}// 最后处理一下,这个一定不能提前处理,因为自己只能买一次// 这个处理是写在外面的,千万别给我写里面去,里面跑的是多重背包for (int t = m; t >= v[u]; t -- ) { // 而且需要注意这个顺序 f[u][t] = w[u] + f[u][t - v[u]];}for (int t = 0; t < v[u]; t ++ )f[u][t] = 0;
}int main()
{// input and build the treecin >> n >> m;int root = -1;memset(h, -1, sizeof h), idx = 0;for (int i = 1; i <= n; i ++ ) {static int p;scanf("%d%d%d", &v[i], &w[i], &p);if (p == -1) {root = i;} else {add(p, i);}}// dp process    memset(st, false, sizeof st);memset(f, 0, sizeof f);dfs(root, m);cout << f[root][m] << endl;return 0;
}

十五、背包问题求方案数

Acwing Link
在这里插入图片描述

这个题目和 原本的背包问题类似,只不过是在维护 原本dp的数组过程中,多维护了一个数组, g[i][j] 用于表示到达当前状态的方案数

#include <bits/stdc++.h>
using namespace std;const int N = 1010;
const int MOD = 1e9 + 7;
int v[N], w[N], n, m;
int g[N][N], f[N][N];int main()
{// inputcin >> n >> m;for (int i = 1; i <= n; i ++ ) {scanf("%d%d", &v[i], &w[i]);}// initialize, 注意这里的初始化可能和我们之前的初始化有些许的不同memset(f, 0, sizeof f);memset(g, 0, sizeof f);for (int i = 0; i <= m; i ++ ) {g[0][i] = 1;}// dpfor (int i = 1; i <= n; i ++ ) {for (int j = 0; j <= m; j ++ ) {if (j >= v[i]) {f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);g[i][j] = 0;// 更新方案数if (f[i][j] == f[i - 1][j]) {g[i][j] += g[i - 1][j];} if (f[i][j] == f[i - 1][j - v[i]] + w[i]) {g[i][j] += g[i - 1][j - v[i]];}} else {// 更新方案数f[i][j] = f[i - 1][j];g[i][j] = g[i - 1][j];}g[i][j] %= MOD;}}// outputcout << g[n][m] << endl;return 0;
}

十六、背包问题求具体方案

Problem Link
在这里插入图片描述
这个题目很有意思,我在这里进行一下详细的讲解
对于原本的递归公式
    f[i][j]=max(f[i−1][j],f[i−1][j−v[i]]+w[i])f[i][j] = max(f[i-1][j], f[i - 1][j - v[i]] + w[i])f[i][j]=max(f[i1][j],f[i1][jv[i]]+w[i]),f[i][j] 表示的是从 1…n 个物品选择部分,使得体积为 j 的最大价值,使用 g[i][j] 可以表示当前状态 f[i][j] 是从哪个状态转移来的。
    为了寻找方案的最小字典序,那我们必须要对 g[i][j] 进行处理,使其满足这个最小字典序的要求,那么我们是让 当前物品 i 被选,还是不被选好呢?

    从直觉上来看,尽可能的让当前物品被选,可能 会好一些,这是因为我们 i 是从 1 -> n 开始枚举的,当然是需要被选中比较好。
    但是,不妨考虑一种情况,f[i][j] (假设 i 为 7)倘若不选自己的话,为 1, 3, 5物品组合,倘若选了自己的话 为 2, 3, 7,显然来看,这种贪心思路是有问题的。

    回到看看,问题到底是出在哪里了呢?因为 f[i][j] 指的是 1…n 个物品中选,对当前物品 i 选不选进行判断,我们对 最后一个物品进行优先的选择,然而最后一个物品,就是方案数中的最后一个数,他是最不重要的,换而言之,我们应当将第一个物品,作为最重要的点,最后判断(因为最后决策的,才是最重要的)

    所以,不难得知,求字典序最大的,也是第一个数决策粒度对大,也应该 放在最后一个进行判断。

为了适应这种变化,我将 f[i][j] 数组的递归方程个真实含义做了改变
f[i][j] 表示从前 i … n 个物品中 选取部分,使得物品体积(代价) 小于等于 j 的最大价值
f[i][j] = max(f[i + 1][j], f[ i + 1][j - v[i]] + w[i]) 而且尽可能的使用后者,即保证能使用 i 物品就使用 i 物品(字典序最小)

#include <bits/stdc++.h>
using namespace std;const int N = 1010;
int f[N][N], g[N][N];
int v[N], w[N];
int n, m;int main() 
{// inputcin >> n >> m;for (int i = 1; i <= n; i ++ ) {scanf("%d%d", &v[i], &w[i]);}// initialize memset(f, 0, sizeof f);memset(g, -1, sizeof f);// dpfor (int i = n; i >= 1; i -- ) {for (int j = 0; j <= m; j ++ ) {if (j >= v[i]) {f[i][j] = max(f[i + 1][j], f[i + 1][j - v[i]] + w[i]);if (f[i][j] == f[i + 1][j - v[i]] + w[i]) {g[i][j] = j - v[i];} else {g[i][j] = j;}} else {f[i][j] = f[i + 1][j];g[i][j] = j;}}}// outputint res = m;for (int i = 1; i <= n; i ++ ) {if (g[i][res] == res) {continue;} else {cout << i << ' ';res = g[i][res];}}puts("");return 0;
}

十七、能量石

Problem Link
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十八、今明的预算方案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/473989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode LCP 22. 黑白方格画

文章目录1. 题目2. 解题1. 题目 小扣注意到秋日市集上有一个创作黑白方格画的摊位。 摊主给每个顾客提供一个固定在墙上的白色画板&#xff0c;画板不能转动。 画板上有 n * n 的网格。绘画规则为&#xff0c;小扣可以选择任意多行以及任意多列的格子涂成黑色&#xff0c;所选…

ubuntu server安装hadoop和spark,并设置集群

安装server请看本人的上一篇博客 Ubuntu Server 20.04.2 安装 先前准备工作 创建 hadoop用户 创建用户之后&#xff0c;输入一下指令重启 shutdown -r now登录 hadoop 用户 安装 ssh 和 vim 首先更新一下 apt 工具 sudo apt-get update然后安装vim sudo apt-get install…

LeetCode 第 35 场双周赛(216/2839,前7.61%)

文章目录1. 比赛结果2. 题目1. LeetCode 5503. 所有奇数长度子数组的和 easy2. LeetCode 5505. 所有排列中的最大和 medium3. LeetCode 5504. 使数组和能被 P 整除 medium4. LeetCode 5506. 奇怪的打印机 II hard1. 比赛结果 做出来3题。继续加油&#xff01; 全国排名&#…

Linux Ubuntu 安装 anaconda3和 Pycharm 社区版本

恰巧大数据实验安装 Anaconda & Pycharm&#xff0c;借此机会写篇博客分享一下 Anaconda 下载 Anaconda 清华镜像网站看一看 下载之后&#xff0c;放入 linux文件夹中 安装 Anaconda bash 下载的文件名 之后&#xff0c;按照提示进行输入信息(大多数都是 回车) 一定…

LeetCode 第 207 场周赛(245/4115,前5.95%)

文章目录1. 比赛结果2. 题目1. LeetCode 5519. 重新排列单词间的空格 easy2. LeetCode 5520. 拆分字符串使唯一子字符串的数目最大 medium3. LeetCode 5521. 矩阵的最大非负积 medium4. LeetCode 5522. 连通两组点的最小成本 hard1. 比赛结果 做出来3题&#xff0c;第四题试了…

《软件工程》individual project开发小记(一)

今天周四没有想去上的课&#xff0c;早八点到中午11点半&#xff0c;下午吃完饭后稍微完善了一下&#xff0c;目前代码可以在dev c和vs2012上正常运行,性能分析我看资料上一大坨,考虑到目前状态不太好,脑袋转不动了,决定先放一放去看看邹老师以前的软工博客以及学长学姐们的经验…

JavaScript中九九乘法表制作

练习一下表格&#xff0c;利用Javascript制作出来&#xff1a; 代码赏析&#xff1a; <!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-s…

机器学习算法--协同过滤算法

0. 关键词 推荐算法长尾理论UserCFItemCF 1. 推荐算法 互联网的飞速发展使我们进入了信息过载的时代&#xff0c;搜索引擎可以帮助我们查找内容&#xff0c;但只能解决明确的需求。为了让用户从海量信息中高效地获得自己所需的信息&#xff0c;推荐系统应运而生。 推荐系统…

利用JavaScript制作星星金字塔

主要练习&#xff1a; 代码赏析&#xff1a; <!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&q…

机器学习算法--ALS

本文转载自&#xff1a;endymecy|ALS 一、什么是ALS ALS是交替最小二乘&#xff08;alternating least squares&#xff09;的简称。在机器学习中&#xff0c;ALS特指使用交替最小二乘求解的一个协同推荐算法。它通过观察到的所有用户给商品的打分&#xff0c;来推断每个用户…

04.卷积神经网络 W2.深度卷积网络:实例探究(作业:Keras教程+ResNets残差网络)

文章目录作业1&#xff1a;Keras教程1. 快乐的房子2. 用Keras建模3. 用你的图片测试4. 一些有用的Keras函数作业2&#xff1a;残差网络 Residual Networks1. 深层神经网络的问题2. 建立残差网络2.1 identity恒等模块2.2 卷积模块3. 建立你的第一个残差网络&#xff08;50层&…

安装Python第三方库的常用方法和注意事项

安装Python的库 这里&#xff0c;我来介绍一下平时我们安装python库的几种常用方法和一些注意事项。 第一种&#xff0c;使用我们的pip工具 第二种&#xff0c;使用IDE中集成的功能进行一键安装(以Pycharm 为例) 第三种&#xff0c;使用Anaconda进行安装 使用 pip 工具安装第三…

LeetCode 968. 监控二叉树(DFS)

文章目录1. 题目2. 解题1. 题目 给定一个二叉树&#xff0c;我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。 计算监控树的所有节点所需的最小摄像头数量。 示例 1&#xff1a; 输入&#xff1a;[0,0,null,0,0] 输出&#xff…

LeetCode 576. 出界的路径数(动态规划)

文章目录1. 题目2. 解题1. 题目 给定一个 m n 的网格和一个球。 球的起始坐标为 (i,j) &#xff0c;你可以将球移到相邻的单元格内&#xff0c;或者往上、下、左、右四个方向上移动使球穿过网格边界。 但是&#xff0c;你最多可以移动 N 次。 找出可以将球移出边界的路径数量…

04.卷积神经网络 W3.目标检测

文章目录1. 目标定位2. 特征点检测3. 目标检测4. 滑动窗口的卷积实现5. Bounding Box预测&#xff08;YOLO&#xff09;6. 交并比7. 非极大值抑制8. Anchor Boxes9. YOLO 算法10. 候选区域作业参考&#xff1a; 吴恩达视频课 深度学习笔记 1. 目标定位 定位分类问题&#xff…

LeetCode 988. 从叶结点开始的最小字符串(DFS)

文章目录1. 题目2. 解题1. 题目 给定一颗根结点为 root 的二叉树&#xff0c;树中的每一个结点都有一个从 0 到 25 的值&#xff0c;分别代表字母 a 到 z&#xff1a;值 0 代表 a&#xff0c;值 1 代表 b&#xff0c;依此类推。 找出按字典序最小的字符串&#xff0c;该字符串…

Humble Numbers USCAO chapter 3.1

...目测我自己写坑定超时,就去NOCOW看了下,题解,官方是每个质数与已有的humble想乘取大于最大humble的最小数即是新的最大humble, 然后我就写了个个,开始嫌麻烦用set存,超时的飞起。然后全部改成数组,结果case 6还是超时,想了半天感觉和别人的题解也没啥差别,为什么运行时间差这…

LeetCode 636. 函数的独占时间(栈)

文章目录1. 题目2. 解题1. 题目 给出一个非抢占单线程CPU的 n 个函数运行日志&#xff0c;找到函数的独占时间。 每个函数都有一个唯一的 Id&#xff0c;从 0 到 n-1&#xff0c;函数可能会递归调用或者被其他函数调用。 日志是具有以下格式的字符串&#xff1a;function_id…

04.卷积神经网络 W3.目标检测(作业:自动驾驶 - 汽车检测)

文章目录1. 问题背景2. YOLO 模型2.1 模型细节2.2 分类阈值过滤2.3 非极大值抑制2.4 完成过滤3. 在照片上测试已预训练的YOLO模型3.1 定义类别、anchors、图片尺寸3.2 加载已预训练的模型3.3 模型输出转化为可用的边界框变量3.4 过滤边界框3.5 在图片上运行测试题&#xff1a;参…

LeetCode 775. 全局倒置与局部倒置(归并排序/二分查找/一次遍历)

文章目录1. 题目2. 解题2.1 归并排序求逆序度2.2 二分查找2.3 一次遍历1. 题目 数组 A 是 [0, 1, ..., N - 1] 的一种排列&#xff0c;N 是数组 A 的长度。 全局倒置指的是 i,j 满足 0 < i < j < N 并且 A[i] > A[j] &#xff0c;局部倒置指的是 i 满足 0 < i…