This commit is contained in:
HuangHai
2025-08-30 18:35:01 +08:00
commit 1f397eca87
6258 changed files with 1487241 additions and 0 deletions

View File

@@ -0,0 +1,232 @@
##[$AcWing$ $898$. 数字三角形](https://www.acwing.com/problem/content/description/900/)
给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。
```cpp {.line-numbers}
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
```
**输入格式**
第一行包含整数 $n$,表示数字三角形的层数。
接下来 $n$ 行,每行包含若干整数,其中第 $i$ 行表示数字三角形第 $i$ 层包含的整数。
**输出格式**
输出一个整数,表示最大的路径数字和。
**数据范围**
$1≤n≤500$,$−10000≤$三角形中的整数$≤10000$
**输入样例:**
```cpp {.line-numbers}
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
```
**输出样例:**
```cpp {.line-numbers}
30
```
### 二、$dfs$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int a[N][N];
int n;
/**
深度优先搜索关键点:
1、递归出口
本题是超出n行,来到了第n+1行,就意味着搜索结束,可以统计答案了
2、每一个位置,面临两种选择,分别是正下方和右下方,需要计算它们每一条路线的总和,
sum(left,right,a[x][y])
3、需要使用int返回,因为从每个坐标出发,最终都需要返回一个sum和,而这个sum对于调用者
是有用的,不用采用全局变量,只能用返回int形式。
4、向下传递,用参数;向上传递,用返回值
*/
int dfs(int x, int y) {
if (x == n + 1) return 0; // 你们不要指望我了,我是0,你们该咋办就咋办吧~
if (y > x) return 0;
return max(dfs(x + 1, y), dfs(x + 1, y + 1)) + a[x][y];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
int res = dfs(1, 1);
printf("%d", res);
return 0;
}
```
结果:简单样例通过
本题数据范围
$1≤n≤500, −10000≤$三角形中的整数$≤10000$
因为爆搜的时间复杂度是 $2^{n-1}$,所以$500$的话就是$2^{499}$,肯定是$TLE$了!需要想办法优化。
### 三、记忆化搜索
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int a[N][N];
int f[N][N]; // 记录以(x,y)坐标位置为根的子树的路径最大和
int n;
int dfs(int x, int y) {
if (f[x][y]) return f[x][y]; // 如果算过,就直接返回
if (x == n + 1) return 0; // 你们不要指望我了,我是0,你们该咋办就咋办吧~
if (y > x) return 0;
// 算过的都记录下来,别丢掉,左下方,x+1,y,右下方,x+1,y+1
f[x + 1][y] = dfs(x + 1, y);
f[x + 1][y + 1] = dfs(x + 1, y + 1);
f[x][y] = max(f[x + 1][y], f[x + 1][y + 1]) + a[x][y];
return f[x][y];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
int res = dfs(1, 1);
printf("%d", res);
return 0;
}
```
### 四、$DP$ 动态规划 [自底向上+二维数组]
* 状态表示
$f[i][j]$
集合:**从底向上** 走到$(i,j)$所有路线的集合
属性:$max$
* 状态计算
$f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j]$
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int n, a[N][N];
int f[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
for (int i = n; i >= 1; i--) // 从下向上去递推
for (int j = 1; j <= i; j++)
f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
printf("%d\n", f[1][1]);
return 0;
}
```
### 五、$DP$ 动态规划 [自底向上+一维数组]
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int f[N];
int a[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
for (int i = n; i >= 1; i--)
for (int j = 1; j <= i; j++)
f[j] = max(f[j + 1], f[j]) + a[i][j];
printf("%d", f[1]);
return 0;
}
```
### 六、$DP$ 动态规划 [自上向下+二维数组]
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int f[N][N];
int a[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
memset(f, -0x3f, sizeof f); // 预求最大,先设最小
// 其实自上而下版本之所以需要初始化-INF,是因为每个枚举到的位置需要从它头顶和它的左上侧取值对比
// 而它的正上方可能是不存在的,如果不初始化,就是0
// 本题数据还可能是负数,就会造成它可会从正上方取来一个0做为最大值,造成结果错误
f[1][1] = a[1][1];
for (int i = 2; i <= n; i++)
for (int j = 1; j <= i; j++)
f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j];
int res = -INF;
for (int i = 1; i <= n; i++) res = max(res, f[n][i]);
printf("%d\n", res);
return 0;
}
```
### 七、$DP$ 动态规划 [自上向下+一维数组]
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int n;
int f[N];
int a[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
// 预求最大,先设最小
memset(f, -0x3f, sizeof f);
f[1] = a[1][1]; // 递推的初始化边界
for (int i = 2; i <= n; i++)
for (int j = i; j >= 1; j--)
f[j] = max(f[j - 1], f[j]) + a[i][j];
int res = -INF;
for (int i = 1; i <= n; i++) res = max(res, f[i]);
printf("%d", res);
return 0;
}
```

View File

@@ -0,0 +1,18 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int n, a[N][N];
int f[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
for (int i = n; i >= 1; i--) // 从下向上去递推
for (int j = 1; j <= i; j++)
f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
printf("%d\n", f[1][1]);
return 0;
}

View File

@@ -0,0 +1,19 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int f[N];
int a[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
for (int i = n; i >= 1; i--)
for (int j = 1; j <= i; j++)
f[j] = max(f[j + 1], f[j]) + a[i][j];
printf("%d", f[1]);
return 0;
}

View File

@@ -0,0 +1,28 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int f[N][N];
int a[N][N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
memset(f, -0x3f, sizeof f); // 预求最大,先设最小
// 其实自上而下版本之所以需要初始化-INF,是因为每个枚举到的位置需要从它头顶和它的左上侧取值对比
// 而它的正上方可能是不存在的,如果不初始化,就是0
// 本题数据还可能是负数,就会造成它可会从正上方取来一个0做为最大值,造成结果错误
f[1][1] = a[1][1];
for (int i = 2; i <= n; i++)
for (int j = 1; j <= i; j++)
f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j];
int res = -INF;
for (int i = 1; i <= n; i++) res = max(res, f[n][i]);
printf("%d\n", res);
return 0;
}

View File

@@ -0,0 +1,25 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
const int INF = 0x3f3f3f3f;
int n;
int f[N];
int a[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
// 预求最大,先设最小
memset(f, -0x3f, sizeof f);
f[1] = a[1][1]; // 递推的初始化边界
for (int i = 2; i <= n; i++)
for (int j = i; j >= 1; j--)
f[j] = max(f[j - 1], f[j]) + a[i][j];
int res = -INF;
for (int i = 1; i <= n; i++) res = max(res, f[i]);
printf("%d", res);
return 0;
}

View File

@@ -0,0 +1,38 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int a[N][N];
int n;
/**
深度优先搜索关键点:
1、递归出口
本题是超出n行,来到了第n+1行,就意味着搜索结束,可以统计答案了
2、每一个位置,面临两种选择,分别是正下方和右下方,需要计算它们每一条路线的总和,
sum(left,right,a[x][y])
3、需要使用int返回,因为从每个坐标出发,最终都需要返回一个sum和,而这个sum对于调用者
是有用的,不用采用全局变量,只能用返回int形式。
4、向下传递,用参数;向上传递,用返回值
*/
int dfs(int x, int y) {
if (x == n + 1) return 0; // 你们不要指望我了,我是0,你们该咋办就咋办吧~
if (y > x) return 0;
return max(dfs(x + 1, y), dfs(x + 1, y + 1)) + a[x][y];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
int res = dfs(1, 1);
printf("%d", res);
return 0;
}

View File

@@ -0,0 +1,27 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int a[N][N];
int f[N][N]; // 记录以(x,y)坐标位置为根的子树的路径最大和
int n;
int dfs(int x, int y) {
if (f[x][y]) return f[x][y]; // 如果算过,就直接返回
if (x == n + 1) return 0; // 你们不要指望我了,我是0,你们该咋办就咋办吧~
if (y > x) return 0;
// 算过的都记录下来,别丢掉,左下方,x+1,y,右下方,x+1,y+1
f[x + 1][y] = dfs(x + 1, y);
f[x + 1][y + 1] = dfs(x + 1, y + 1);
f[x][y] = max(f[x + 1][y], f[x + 1][y + 1]) + a[x][y];
return f[x][y];
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
cin >> a[i][j];
int res = dfs(1, 1);
printf("%d", res);
return 0;
}

View File

@@ -0,0 +1,41 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
const int M = 1010;
int n, m;
int f[N][N];
char str[M][N];
int edit_distance(char a[], char b[]) {
int la = strlen(a + 1), lb = strlen(b + 1);
for (int i = 0; i <= lb; i++) f[0][i] = i;
for (int i = 0; i <= la; i++) f[i][0] = i;
for (int i = 1; i <= la; i++)
for (int j = 1; j <= lb; j++) {
// 增加和删除
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
// 修改
if (a[i] == b[j])
f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else // 相等
f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
return f[la][lb];
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> (str[i] + 1); // 二维数组,躲开第一列
while (m--) {
char s[N];
int limit;
cin >> (s + 1) >> limit;
int res = 0;
for (int i = 0; i < n; i++)
if (edit_distance(str[i], s) <= limit) res++;
cout << res << endl;
}
return 0;
}

View File

@@ -0,0 +1,91 @@
##[$AcWing$ $899$. 编辑距离](https://www.acwing.com/problem/content/description/901/)
### 一、题目描述
给定 $n$ 个长度不超过 $10$ 的字符串以及 $m$ 次询问,每次询问给出一个字符串和一个操作次数上限。
对于每次询问,请你求出给定的 $n$ 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。
**输入格式**
第一行包含两个整数 $n$ 和 $m$。
接下来 $n$ 行,每行包含一个字符串,表示给定的字符串。
再接下来 $m$ 行,每行包含一个字符串和一个整数,表示一次询问。
字符串中只包含小写字母,且长度均不超过 $10$。
**输出格式**
输出共 $$ 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。
**数据范围**
$1≤n,m≤1000$
**输入样例**:
```cpp {.line-numbers}
3 2
abc
acd
bcd
ab 1
acbd 2
```
**输出样例**:
```cpp {.line-numbers}
1
3
```
### 二、理解与感悟
多次使用最小编辑距离模板,反复使用之,最短编辑距离的时间复杂度是$O(N^2)$,本题的字符串长度上限是$10$,就是$10^2=100$。
一共有$n<=1000$个,$m<=1000$次询问,所以算法复杂度是$10^2 * 10^3 * 10^3=10^8$,本题的时间要求是$2$秒,还是可以完成的。
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 15;
const int M = 1010;
int n, m;
int f[N][N];
char str[M][N];
int edit_distance(char a[], char b[]) {
int la = strlen(a + 1), lb = strlen(b + 1);
for (int i = 0; i <= lb; i++) f[0][i] = i;
for (int i = 0; i <= la; i++) f[i][0] = i;
for (int i = 1; i <= la; i++)
for (int j = 1; j <= lb; j++) {
// 增加和删除
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
// 修改
if (a[i] == b[j])
f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else // 相等
f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
return f[la][lb];
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> (str[i] + 1); // 二维数组,躲开第一列
while (m--) {
char s[N];
int limit;
cin >> (s + 1) >> limit;
int res = 0;
for (int i = 0; i < n; i++)
if (edit_distance(str[i], s) <= limit) res++;
cout << res << endl;
}
return 0;
}
```

View File

@@ -0,0 +1,31 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
// 最短编辑距离
int main() {
cin >> n >> a + 1 >> m >> b + 1;
// 初始化
for (int i = 0; i <= m; i++) f[0][i] = i;
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
// 增加和删除
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
// 修改
if (a[i] == b[j])
f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else // 相等
f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
cout << f[n][m] << endl;
return 0;
}

View File

@@ -0,0 +1,129 @@
##[$AcWing$ $902$. 最短编辑距离](https://www.acwing.com/problem/content/description/904/)
### 一、题目描述
给定两个字符串 $A$ 和 $B$,现在要将 $A$ 经过若干操作变为 $B$,可进行的操作有:
1. 删除–将字符串 $A$ 中的某个字符删除。
2. 插入–在字符串 $A$ 的某个位置插入某个字符。
3. 替换–将字符串 $A$ 中的某个字符替换为另一个字符。
现在请你求出,将 $A$ 变为 $B$ 至少需要进行多少次操作。
**输入格式**
第一行包含整数 $n$,表示字符串 $A$ 的长度。
第二行包含一个长度为 $n$ 的字符串 $A$。
第三行包含整数 $m$,表示字符串 $B$ 的长度。
第四行包含一个长度为 $m$ 的字符串 B。
字符串中均只包含大小写字母。
**输出格式**
输出一个整数,表示最少操作次数。
**数据范围**
$1≤n,m≤1000$
**输入样例**:
```cpp {.line-numbers}
10
AGTCTGACGC
11
AGTAAGTAGGC
```
**输出样例**:
```cpp {.line-numbers}
4
```
### 二、解题思路
与$LCS$(最长公共子序列)问题的状态表示是很接近的。
![QQ截图20210315105007.png](https://cdn.acwing.com/media/article/image/2021/03/15/64630_a5de8f6c85-QQ截图20210315105007.png)
#### 状态表示
$f[i,j]$:$a[1..i]$变成$b[1..j]$的操作步数。
现在要求的是:两个字符串的最短编辑距离,由于我们不知道最短编辑距离是多少,就希望知道 **当前状态是从哪些状态转移过来**,前面的状态可以通过 **增加、删除、修改转移过来** 。
#### 属性
操作数的最小值,$min$。
#### 状态计算
按题意划分为删除,增加,修改,相等四种情况
- 删除:把$a[i]$删掉之后$a[1\sim i]$和$b[1\sim j]$匹配.所以没删除之前,就有$a[1 \sim i-1]$与$b[1 \sim j]$匹配
$$\large f[i][j]=f[i-1][j] + 1$$
- 插入:插入之后$a[i]$与$b[j]$完全匹配,所以插入的就是$b[j]$,那填之前$a[1\sim i]$和$b[1\sim (j-1)]$匹配
$$\large f[i][j]=f[i][j-1] + 1$$
- 替换:把$a[i]$改成$b[j]$之后想要$a[1\sim i]$与$b[1\sim j]$匹配,那么修改这一位之前,$a[1\sim (i-1)]$应该与$b[1\sim (j-1)]$匹配
$$\large f[i][j]=f[i-1][j-1] + 1$$
- 相等:如果本来$a[i]$与$b[j]$这一位上就相等,那么不用改,即
$$\large f[i][j]=f[i-1][j-1]$$
**结论**
$$f[i][j] = min(f[i-1][j]+1,f[i][j-1]+1,f[i-1][j-1]+(a[i] == b[j]?0:1))$$
#### 初始化
细节问题:初始化怎么搞
先考虑有哪些初始化嘛
![](http://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/2023/01/98e5af3dd29fbb0d4ba00f103748d053.png)
- $f[0][i]$:如果$a$初始长度就是$0$,那么只能用插入操作让它变成$b$
- $f[i][0]$:如果$b$的长度是$0$,那么$a$只能用删除操作让它变成$b$
- $f[i][j]$ = $INF$ 其它初始化为$INF$
### 二、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
// 最短编辑距离
int main() {
cin >> n >> a + 1 >> m >> b + 1;
// 初始化
for (int i = 0; i <= m; i++) f[0][i] = i;
for (int i = 0; i <= n; i++) f[i][0] = i;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
// 增加和删除
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
// 修改
if (a[i] == b[j])
f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else // 相等
f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
cout << f[n][m] << endl;
return 0;
}
```