转载请注明出处:https://hts0000.github.io/
欢迎与我联系:hts_0000@sina.com
线性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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 510
const INF int = 1e9
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
// 存放数字三角形
nums [N][N]int
// dp 数组
// f[i][j] 表示从起点走到 [i,j] 点的最大路径和
f [N][N]int
// n 行的数字三角形
n int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n)
for i := 1; i <= n; i++ {
for j := 1; j <= i; j++ {
fmt.Fscan(in, &nums[i][j])
}
}
// 初始化为 -INF
// 因为是从上往下遍历,会用到 [i-1][j]
// 因此每一行需要多初始化一列,给下一行的使用
for i := 0; i <= n; i++ {
for j := 0; j <= i + 1; j++ {
f[i][j] = -INF
}
}
// 初始化起点,表示从起点走到起点的最大路径和
f[1][1] = nums[1][1]
for i := 2; i <= n; i++ {
for j := 1; j <= i; j++ {
f[i][j] = max(f[i-1][j-1], f[i-1][j]) + nums[i][j]
}
}
ans := -INF
// 遍历最后一行,获取最大路径和
for i := 1; i <= n; i++ {
ans = max(ans, f[n][i])
}
fmt.Fprintln(out, ans)
}
func max(a, b int) int {
if a > b { return a }
return b
}
|
从下往上
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
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 510
const INF int = 1e9
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
// 存放数字三角形
nums [N][N]int
// dp 数组
// f[1][1] 表示从最下面一层走到起点的最大路径和
f [N][N]int
// n 行的数字三角形
n int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n)
for i := 1; i <= n; i++ {
for j := 1; j <= i; j++ {
fmt.Fscan(in, &nums[i][j])
}
}
// 从下往上遍历,只会用到三角形内的值,因此不用初始化
for i := n; i >= 1; i-- {
for j := i; j >= 1; j-- {
f[i][j] = max(f[i+1][j], f[i+1][j+1]) + nums[i][j]
}
}
fmt.Fprintln(out, f[1][1])
}
func max(a, b int) int {
if a > b { return a }
return b
}
|
经典模板题
898. 数字三角形
剑指 Offer II 100. 三角形中最小路径之和
最长上升子序列
最长上升子序列朴素
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
51
52
53
54
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 1010
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
// 存储输入
a [N]int
// dp 数组,f[i] 表示以 i 结尾的最长子序列长度
f [N]int
n int
)
func main() {
defer out.Flush()
// 处理输入
fmt.Fscan(in, &n)
for i := 1; i <= n; i++ { fmt.Fscan(in, &a[i]) }
// 初始化,每个以 i 结尾的子序列长度至少为1
for i := 1; i <= n; i++ { f[i] = 1 }
// 枚举每个数字结尾
for i := 1; i <= n; i++ {
// 遍历 i 前面所有数字
for j := 1; j <= i; j++ {
// 题目要求严格小于
if a[j] < a[i] {
// f[j] 表示以 j 结尾的最长子序列长度,加1表示加上 i 的长度
f[i] = max(f[i], f[j] + 1)
}
}
}
// 遍历一遍 dp 数组获取最大值
ans := 0
for i := 1; i <= n; i++ { ans = max(ans, f[i]) }
fmt.Fprintln(out, ans)
}
func max(a, b int) int {
if a > b { return a }
return b
}
|
最长上升子序列优化
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
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 100010
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
a [N]int
f [N]int
n int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n)
for i := 0; i < n; i++ { fmt.Fscan(in, &a[i]) }
length := 0
for i := 0; i < n; i++ {
l, r := 0, length
for l < r {
mid := (l + r + 1) >> 1
if f[mid] < a[i] {
l = mid
} else {
r = mid - 1
}
}
length = max(length, r + 1)
f[r + 1] = a[i]
}
fmt.Fprintln(out, length)
}
func max(a, b int) int {
if a > b { return a }
return b
}
|
经典模板题
895. 最长上升子序列
896. 最长上升子序列 II
最长公共子序列
最长公共子序列
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
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 1010
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
a, b string
// f[i][j] 表示在 a 字符串前 i 个字符中出现,且在 b 字符串前 j 个字符中出现的最长公共子序列长度
f [N][N]int
n, m int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n, &m)
fmt.Fscan(in, &a, &b)
// 让字符串从下标1开始
a = " " + a
b = " " + b
for i := 1; i <= n; i ++ {
for j := 1; j <= m; j ++ {
f[i][j] = max(f[i-1][j], f[i][j-1])
if a[i] == b[j] {
f[i][j] = max(f[i][j], f[i-1][j-1] + 1)
}
}
}
fmt.Fprintln(out, f[n][m])
}
func max(a, b int) int {
if a > b { return a }
return b
}
|
经典模板题
897. 最长公共子序列
剑指 Offer II 095. 最长公共子序列
编辑距离
最短编辑距离
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
51
52
53
54
55
56
57
58
59
60
61
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 1010
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
a, b string
// f[i][j] 表示将 a 的前 i 个字符变成 b 的前 j 个字符最少需要多少步
f [N][N]int
// 字符串长度
n, m int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n, &a)
fmt.Fscan(in, &m, &b)
a = " " + a
b = " " + b
// 表示将 a 的前0个字符变成 b 的前 i 个字符最少需要多少步
for i := 1; i <= m; i++ { f[0][i] = i }
// 表示将 a 的前 i 个字符变成 b 的前 0 个字符最少需要多少步
for i := 1; i <= n; i++ { f[i][0] = i }
for i := 1; i <= n; i++ {
for j := 1; j <= m; j++ {
// f[i][j-1]+1 表示 a 的前 i 个字符与 b 的前 j-1 个字符相同需要的最小步骤,
// +1 表示为 a 字符串增加一个字符,步骤数 +1
// f[i-1][j]+1 表示 a 的前 i-1 个字符与 b 的前 j 个字符相同需要的最小步骤,
// +1 表示为 a 字符串删除一个字符,步骤数 +1
f[i][j] = min(f[i][j-1]+1, f[i-1][j]+1)
// 当前字符不相同,需要修改当前字符
if a[i] != b[j] {
// f[i-1][j-1] 表示 a 的前 i-1 个字符与 b 的前 j-1 个字符相同需要的最小步骤,
// +1 表示将 a 的 i 字符修改为 b 的 j 字符,步骤数 +1
f[i][j] = min(f[i][j], f[i-1][j-1]+1)
// 如果当前字符相同,那么当前步骤数就是使得 a 的前 i-1 个字符与 b 的前 j-1 个字符相同需要的最小步骤
} else {
f[i][j] = min(f[i][j], f[i-1][j-1])
}
}
}
fmt.Fprintln(out, f[n][m])
}
func min(a, b int) int {
if a < b { return a }
return b
}
|
经典模板题
902. 最短编辑距离
72. 编辑距离
899. 编辑距离
区间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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 310
const INF int = 1e9
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
// 存储输入和前缀和
s [N]int
// f[i][j] 表示合并 [i~j] 堆石子所需最小代价
f [N][N]int
n int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n)
// 读取输入
for i := 1; i <= n; i++ { fmt.Fscan(in, &s[i]) }
// 处理前缀和
for i := 1; i <= n; i++ { s[i] += s[i-1] }
// f[i][i] 单独一堆的石子,合并单独一堆石子代价为0
for i := 1; i <= n; i++ { f[i][i] = 0 }
// length 枚举区间范围,从 2 开始是因为上面已经初始化了区间范围为 1 的情况
for length := 2; length <= n; length++ {
// 枚举区间左端点
for i := 1; i + length - 1 <= n; i++ {
// l, r 表示每一个 [l,r] 小区间,最终枚举 [1,n] 整个区间
// [1,2], [2,3], [3,4], ...
// [1,3], [2,4], ...
// [1,4], ...
l, r := i, i + length - 1
// 表示合并 [l,r] 这个区间代价一开始为无穷
f[l][r] = INF
// 以 k 为中点,划分为左右两块区间,
// f[i][j] 的值就为合并左区间的最小代价 f[l][k] + 合并右区间的最小代价 f[k+1][r] + 合并左右区间的代价 s[r] - s[l-1]
// s[r] - s[l-1] 用前缀和快速求 [l,r] 这段区间的值
// 为什么要加上 [l,r] 这段区间的值?因为最后还需要合并左右两堆石子
for k := l; k < r; k++ {
f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] + s[r] - s[l-1])
}
}
}
fmt.Fprintln(out, f[1][n])
}
func min(a, b int) int {
if a < b { return a }
return b
}
|
计数类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
29
30
31
32
33
34
35
36
37
|
package main
import (
"bufio"
"fmt"
"os"
)
const N int = 1010
const MOD int= 1e9 + 7
var (
in = bufio.NewReader(os.Stdin)
out = bufio.NewWriter(os.Stdout)
f [N]int
n int
)
func main() {
defer out.Flush()
fmt.Fscan(in, &n)
f[0] = 1
// 将整数划分看成是完全背包问题
// f[j] 表示 1~n 当中选,体积恰好是 j 的方案数
for i := 1; i <= n; i++ {
for j := i; j <= n; j++ {
f[j] = (f[j] + f[j-i]) % MOD
}
}
fmt.Fprintln(out, f[n])
}
|