0%

动态规划

背包问题

背包问题(Knapsack Problem)是组合优化中的经典问题之一,在计算机科学、运筹学以及算法竞赛中具有重要地位。其核心思想是,在给定的容量限制下,如何选择物品使得总价值最大化。

01背包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <bits/stdc++.h>
#define int long long
using namespace std;

// 只从前 i 个物品中选
// 总体积 <= j
// f(i, j) 为最大价值

// f(i, j)可以划分为两个集合, 一个不包含i, 一个包含i
// 不包含i, 就是从1 ~ i - 1选 <= j, 也就是f(i - 1, j)
// 包含i, 我们可以先不看i, 也就是f(i - 1, j - vi), 再加上第 i 个物品的价值
// f(i - 1, j - vi) + wi
// f(i, j) = max(f(i - 1, j), f(i - 1, j - vi) + wi))

const int N = 1111;

int n, m;
int f[N][N];
int v[N], w[N];

void solve() {
// n为数量,m为容积
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
// 如果i = 0,显然f(0, j) = 0
for (int i = 1; i <= n; i++) {
for (int j = 0; 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]);
}
}
cout << f[n][m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

01背包(一维)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
#define int long long
using namespace std;

// 优化为一维数组

const int N = 1111;

int n, m;
// f[j] 表示容量为 j 时的最大价值
int f[N];
int v[N], w[N];

void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
// 之前的代码每次变化都是 i - 1
// 所以可以直接覆盖
// 从后往前覆盖可以避免重复
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

完全背包(暴力)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#define int long long
using namespace std;

// 完全背包问题,每件物品可以无限次使用
// 对于f[i][j],可以分为若干个集合
// 第 i 个物品选 0, 1, 2, 3, ..., k 个
// 1. 去掉 k 个物品 i
// 2. f[i - 1][j - k * v[i]] + k * w[i]

const int N = 1111;
int v[N], w[N];
int f[N][N];

void solve() {
// n 个物品,总体积 m
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; 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;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

完全背包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#define int long long
using namespace std;

// 完全背包问题,每件物品可以无限次使用
// 对于f[i][j],可以分为若干个集合
// 第 i 个物品选 0, 1, 2, 3, ..., k 个
// 1. 去掉 k 个物品 i
// 2. f[i - 1][j - k * v[i]] + k * w[i]

// 优化思路 f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w, f[i - 1][j - 2v] + 2w, f[i - 1][j - 3v] + 3w)
// f[i][j - v] = max( f[i - 1][j - v], f[i - 1][j - 2v] + w, f[i - 1][j - 3v] + 2w, ...)
// 上面的每一个都比下面的多 w
// 所以最大值也会多w, 就是f[i, j - v] + w

const int N = 1111;
int v[N], w[N];
int f[N][N];

void solve() {
// n 个物品,总体积 m
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

完全背包(一维)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#define int long long
using namespace std;

const int N = 1111;
int v[N], w[N];
int f[N];

void solve() {
// n 个物品,总体积 m
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
// 顺序遍历,因为每次是更新都是 i, 而不是 i - 1
// 所以用更新完之后的数据
for (int j = 0; j <= m; j++) {
if (j >= v[i]) f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

多重背包(暴力)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#define int long long
using namespace std;

// 多重背包问题
// f[i][j] = max(f[i - 1][j - v[i] * k] + w[i] * k)
// k = 0, 1, 2, 3, ..., s[i]

const int N = 1111;
int v[N], w[N], s[N];
int f[N][N];

void solve() {
int n, m; cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> 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] && j >= k * v[i]; k++) {
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
cout << f[n][m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

多重背包(二进制优化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 25000, M = 2010;

int n, m;
int v[N], w[N];
int f[N];

void solve() {
cin >> n >> m;
int cnt = 0;
// 二进制优化
// 把原来的物品分为若干个1, 2, 4, ... 的子物品
// 每个子物品都可以组合为原物品的所有状态
for (int i = 1; i <= n; i++) {
int a, b, s;
cin >> a >> b >> s;
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;
}
}
// 转化为 01 背包问题
n = cnt;
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]);
}
}
cout << f[m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

分组背包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>
#define int long long
using namespace std;

// 分组背包问题
// 每组物品只能选一个
// f[i][j] 在前 i 个物品中选,总体积 <= j,f[i][j]为最大价值
// 第 i 组物品一个都不选 f[i - 1][j]
// 从第 i 组中选第 k 个物品 f[i - 1, j - v[i, k]] + w[i, k]

const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];

// 对于一维的优化
// 用的是上一层的数据的话

// 从大到小枚举
// 用的是这一层的数据的话
// 从小到大枚举

void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
for (int j = 1; j <= s[i]; j++) {
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 1; k <= s[i]; k++) {
if (j >= v[i][k]) {
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
}

}
}
}
cout << f[m] << endl;
}

signed main() {
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

线性DP

数字三角形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
int n; cin >> n;
vector<vector<int>> arr(n + 1, vector<int>(n + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> arr[i][j];
}
}
// 从倒数第二行开始
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
arr[i][j] += max(arr[i + 1][j], arr[i + 1][j + 1]);
}
}
cout << arr[1][1] << endl;
}

signed main () {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

最长上升子序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
using namespace std;
#define int long long

// 最长上升子序列
// 状态表示 f[i]
// 集合:所有以第 i 个元素结尾的上升子序列
// 属性;所有以第 i 个元素结尾的上升子序列长度的最大值
// 状态计算
// f[i] = max(f[j] + 1), j = 0, 1, 2, 3, ..., i - 1.

const int N = 1111;
int n;
int a[N];
int f[N];

void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
// 计算所有以 i 结尾的最长上升子序列的长度
for (int i = 1; i <= n; i++) {
f[i] = 1; // 只有一个数
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) {
f[i] = max(f[i], f[j] + 1);
}
}
}
int ans = INT_MIN;
for (int i = 1; i <= n; i++) {
ans = max(ans, f[i]);
}
cout << ans << endl;
}

signed main () {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}

一维动态规划

斐波那契数列

https://leetcode.cn/problems/fibonacci-number/

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public:
int fib(int n) {
vector<int> f(31);
f[0] = 0; f[1] = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i - 1] + f[i - 2];
}
return f[n];
}
};

最低票价

1