Acwing-基础算法课笔记之动态规划(线性DP)
- 一、数字三角形
- 1、概述
- 2、闫氏dp分析法
- 代码示例
- 二、最长上升子序列
- 1、概述
- 2、闫氏dp分析法
- 3、过程模拟
- 4、代码演示
- 三、最长上升子序列强化版
- 1、概述
- 2、代码示例
- 四、最长公共子序列(LCS)
- 1、定义
- (1)分解
- (2)子问题
- 2、过程模拟
- 3、代码示例
- 五、最短编辑距离
- 1、定义
- (1)分解
- (2)子问题
- 2、过程模拟
- 3、代码示例
一、数字三角形
1、概述
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
2、闫氏dp分析法
代码示例
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 510;
int n;
int dp[N][N], w[N][N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
scanf("%d", &w[i][j]);
}
}
for (int i = 1; i <= n; i++)dp[n][i] = w[n][i];//因为从底部开始往上寻找,所以将底部的dp先初始化
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
dp[i][j] = max(dp[i + 1][j] + w[i][j], dp[i + 1][j + 1] + w[i][j]);
}
}
printf("%d", dp[1][1]);
return 0;
}
二、最长上升子序列
1、概述
给定一个长度为N的数列A,求数量单调递增的子序列的长度最长是多少。A的任意子序列B可表示为 B = { A k 1 , A k 2 , . . . , A k p } B=\{A_{k_1},A_{k_2},...,A_{k_p}\} B={Ak1,Ak2,...,Akp},其中 k 1 < k 2 < ⋯ < k p k_1<k_2<\cdots<k_p k1<k2<⋯<kp。
2、闫氏dp分析法
3、过程模拟
如何理解所有以第i个数结尾的上升子序列
例如: 以
8
8
8为第
i
i
i个数,则
8
8
8前面的上升子序列为:
3
,
8
3,8
3,8
1
,
8
1,8
1,8
2
,
8
2,8
2,8
1
,
2
,
8
1,2,8
1,2,8
4、代码演示
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N], dp[N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j <= i; j++) {
if (a[j] < a[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, dp[i]);
}
printf("%d", ans);
return 0;
}
三、最长上升子序列强化版
1、概述
由于数据范围比较大,所以只能利用二分的思想先筛选出序列中最大的数,找出该数前的最大上升子序列。
2、代码示例
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int n;
int a[N], dp[N];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++)scanf("%d", &a[i]);
int len = 0;
dp[0] = -2e9;
for (int i = 0; i < n; i++) {
int l = 0, r = len;
while (l < r) {
int mid = l + r + 1 >> 1;
if (dp[mid] < a[i])l = mid;
else r = mid - 1;
}
len = max(len, r + 1);
dp[r + 1] = a[i];
}
printf("%d", len);
return 0;
}
四、最长公共子序列(LCS)
1、定义
例如:
s
1
:
A
B
C
B
D
A
B
s_1:ABCBDAB
s1:ABCBDAB
s
2
:
B
D
C
A
B
C
s_2:BDCABC
s2:BDCABC
D
[
i
]
[
j
]
=
s
1
D[i][j]=s_1
D[i][j]=s1前
i
i
i个字符与
s
2
s_2
s2前
j
j
j个字符的
L
C
S
LCS
LCS
例如:
D
[
7
]
[
6
]
=
4
D[7][6]=4
D[7][6]=4
(1)分解
1、 D [ i ] [ j ] = D [ i − 1 ] [ j − 1 ] + 1 D[i][j]=D[i-1][j-1]+1 D[i][j]=D[i−1][j−1]+1
2、 D [ i ] [ j ] = m a x ( D [ i − 1 ] [ j ] D [ i ] [ j − 1 ] ) D[i][j]=max\begin{pmatrix} D[i-1][j] \\ D[i][j-1] \end{pmatrix} D[i][j]=max(D[i−1][j]D[i][j−1])
(2)子问题
D 00 = 0 , D i 0 = 0 , D 0 j = 0 D_{00}=0,D_{i0}=0,D_{0j}=0 D00=0,Di0=0,D0j=0
2、过程模拟
⋇
\divideontimes
⋇如果两个字符串的末尾字符相同,分析如下:
∙ \bullet ∙ D [ i ] [ j ] = D [ i − 1 ] [ j − 1 ] + 1 D[i][j]=D[i-1][j-1]+1 D[i][j]=D[i−1][j−1]+1
⋇ \divideontimes ⋇如果两个字符串的末尾字符不同需要舍弃两字符串中的其中一个末尾的字符 x x x 或 y y y,分析如下:
∙ \bullet ∙如果舍弃 s 1 s_1 s1 的 x x x,则 D [ i ] [ j ] = m a x ( D [ i − 1 ] [ j ] ) D[i][j]=max(D[i-1][j]) D[i][j]=max(D[i−1][j])
∙ \bullet ∙如果舍弃 s 2 s_2 s2 的 y y y,则 D [ i ] [ j ] = m a x ( D [ i ] [ j − 1 ] ) D[i][j]=max(D[i][j-1]) D[i][j]=max(D[i][j−1])
画表格来描述:
1、 D [ i ] [ j ] = D [ i − 1 ] [ j − 1 ] + 1 , s 1 [ i − 1 ] = s 2 [ j − 1 ] D[i][j]=D[i-1][j-1]+1,s_1[i-1]=s_2[j-1] D[i][j]=D[i−1][j−1]+1,s1[i−1]=s2[j−1]
2、 D [ i ] [ j ] = m a x ( D [ i − 1 ] [ j ] D [ i ] [ j − 1 ] ) , s 1 [ i − 1 ] ! = s 2 [ j − 1 ] D[i][j]=max\begin{pmatrix} D[i-1][j] \\ D[i][j-1] \end{pmatrix},s_1[i-1]!=s_2[j-1] D[i][j]=max(D[i−1][j]D[i][j−1]),s1[i−1]!=s2[j−1]
∅ \varnothing ∅ | B | D | C | A | B | C | |
---|---|---|---|---|---|---|---|
∅ \varnothing ∅ | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | ||||||
B | 0 | ||||||
C | 0 | ||||||
B | 0 | ||||||
D | 0 | ||||||
A | 0 | ||||||
B | 0 |
⇓ \Darr ⇓
∅ \varnothing ∅ | B | D | C | A | B | C | |
---|---|---|---|---|---|---|---|
∅ \varnothing ∅ | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
A | 0 | 0 | 0 | 0 | 1 | 1 | 1 |
B | 0 | 1 | 1 | 1 | 1 | 2 | 2 |
C | 0 | 1 | 1 | 2 | 2 | 2 | 3 |
B | 0 | 1 | 1 | 2 | 2 | 3 | 3 |
D | 0 | 1 | 2 | 2 | 2 | 3 | 3 |
A | 0 | 1 | 2 | 2 | 3 | 3 | 3 |
B | 0 | 1 | 2 | 2 | 3 | 4 | 4 |
方法: 看坐标 ( i , j ) (i,j) (i,j)的元素是否相等,如果相等则以左斜上方的数为基础加 1 1 1,否则等于左边的数。
如果不理解可以看一看这位大佬的视频
3、代码示例
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
int dp[N][N];
char a[N], b[N];
int main() {
cin >> n >> m;
cin >> a + 1;
cin >> b + 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i] == b[j])dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
cout << dp[n][m];
return 0;
}
五、最短编辑距离
1、定义
D
[
i
]
[
j
]
D[i][j]
D[i][j]等于
s
1
s_1
s1前
i
i
i个字符编辑为
s
2
s_2
s2的前
j
j
j个字符它的编辑距离。
例如:
s
u
n
sun
sun编辑为
s
a
t
u
r
satur
satur,则
D
35
=
3
D_{35}=3
D35=3。
(1)分解
⋇
\divideontimes
⋇如果
s
1
s_1
s1和
s
2
s_2
s2的末尾字符相同,则只要编辑
s
i
−
1
s_{i-1}
si−1的字符串变成
s
j
−
1
s_{j-1}
sj−1的字符串的次数
∙
\bullet
∙如果
s
i
−
1
s_{i-1}
si−1等于
s
j
−
1
s_{j-1}
sj−1,则
D
[
i
]
[
j
]
=
D
[
i
−
1
]
[
j
−
1
]
D[i][j]=D[i-1][j-1]
D[i][j]=D[i−1][j−1]
⋇
\divideontimes
⋇如果
s
1
s_1
s1和
s
2
s_2
s2的末尾字符不相同
∙
\bullet
∙如果
s
1
s_1
s1的字符串末尾与
s
2
s_2
s2相比少了一个
y
y
y,则在
s
1
s_1
s1的末尾插入
y
y
y,则状态转移方程为:
D
[
i
]
[
j
]
=
D
[
i
]
[
j
−
1
]
+
1
D[i][j]=D[i][j-1]+1
D[i][j]=D[i][j−1]+1
∙ \bullet ∙如果 s 1 s_1 s1的字符串末尾与 s 2 s_2 s2相比多了一个 x x x,则删除 s 1 s_1 s1末尾的 x x x,则状态转移方程为: D [ i ] [ j ] = D [ i − 1 ] [ j ] + 1 D[i][j]=D[i-1][j]+1 D[i][j]=D[i−1][j]+1
∙ \bullet ∙如果 s 1 s_1 s1与 s 2 s_2 s2具有高度的相似性,则将 s 1 s_1 s1当中的某个字符替换成 s 2 s_2 s2当中的某个字符,则状态转移方程为: D [ i ] [ j ] = D [ i − 1 ] [ j − 1 ] + 1 D[i][j]=D[i-1][j-1]+1 D[i][j]=D[i−1][j−1]+1
在这三种条件中选最小
(2)子问题
D 00 = 0 , D i 0 = i , D 0 j = j D_{00}=0,D_{i0}=i,D_{0j}=j D00=0,Di0=i,D0j=j
2、过程模拟
∅ \varnothing ∅ | s | a | t | u | r | |
---|---|---|---|---|---|---|
∅ \varnothing ∅ | 0 | 1 | 2 | 3 | 4 | 5 |
s | 1 | |||||
u | 2 | |||||
n | 3 |
⇓ \Darr ⇓
∅ \varnothing ∅ | s | a | t | u | r | |
---|---|---|---|---|---|---|
∅ \varnothing ∅ | 0 | 1 | 2 | 3 | 4 | 5 |
s | 1 | 0 | 1 | 2 | 3 | 4 |
u | 2 | 1 | 1 | 2 | 2 | 3 |
n | 3 | 2 | 2 | 2 | 3 | 3 |
方法: 如果所在同一坐标的两个字符相等,则该坐标的值等于左上方坐标的值。如果不相等,则需要考虑是插入、删除还是替换。如果是插入,则当前坐标的值等于左边坐标的值加 1 1 1。如果是删除,则当前坐标的值等于上方的值加 1 1 1。如果是替换,则当前坐标的值等于左上方的值加 1 1 1。
3、代码示例
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int dp[N][N];
int main() {
scanf("%d%s", &n, a + 1);
scanf("%d%s", &m, b + 1);
for (int i = 1; i <= n; i++)dp[i][0] = i;
for (int j = 1; j <= m; j++)dp[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = min(dp[i][j - 1] + 1, dp[i - 1][j] + 1);
if (a[i] == b[j])dp[i][j] = min(dp[i][j], dp[i - 1][j - 1]);
else dp[i][j] = min(dp[i - 1][j - 1] + 1, dp[i][j]);
}
}
printf("%d\n", dp[n][m]);
return 0;
}