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,207 @@
## [$AcWing$ $1076$. 迷宫问题](https://www.acwing.com/problem/content/1078/)
### 一、题目描述
给定一个 $n×n$ 的二维数组,如下所示:
```cpp {.line-numbers}
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 0, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};
```
它表示一个迷宫,其中的$1$表示墙壁,$0$表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的最短路线。
数据保证至少存在一条从左上角走到右下角的路径。
**输入格式**
第一行包含整数 $n$。
接下来 $n$ 行,每行包含 $n$ 个整数 $0$ 或 $1$,表示迷宫。
**输出格式**
输出从左上角到右下角的最短路线,如果答案不唯一,输出任意一条路径均可。
按顺序,每行输出一个路径中经过的单元格的坐标,左上角坐标为 $(0,0)$,右下角坐标为 $(n−1,n−1)$。
**数据范围**
$0≤n≤1000$
**输入样例**:
```cpp {.line-numbers}
5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
```
**输出样例**:
```cpp {.line-numbers}
0 0
1 0
2 0
2 1
2 2
2 3
2 4
3 4
4 4
```
### 二、本题学习目的
练习使用广度优先搜索时,记录路径的办法,可以采用倒序的广度优先搜索,也可以采用正序的广度优先搜索,但倒序的代码简单些,正序的麻烦些。
### 三、倒序记录
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
// 边权为1的时候,可以使用bfs求最短路径
// 学习:bfs中如何记录路径,就是把bool st[N][N]修改为pair类型,记录前序是哪个点
// 这样,st就不光可以记录走没走过(没走过的话,值为空,走过的话有PII值),而且可以通过PII反推回走的路线
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 1010, M = N * N;
int n;
int g[N][N]; // 地图,1是墙壁,0是可以走的路,4个方向可以走
PII pre[N][N];
// 前序坐标,每个位置,都需要记录它的前序来源坐标,
// 如果来源坐标还是默认的初始值-1,表示没有走过;如果来源坐标里有一个数对,就知道它是由某个位置走过来的,就是走过了
// 参数:x,y 坐标,值:前驱是哪个节点PII,相当于一个PII组成的链条,从最后一个出发,不断的找自己的前驱,就可以找到起点
// 配合一个 while(true)的循环,不断的向前找前驱,就可以走完最短路径的全程,至于是不是反了
// 背一下结论: 
// 倒着推:while(true)直接输出
// 正着推:需要配合vecotr<PII>进行一次暂存,然后再倒序输出即可
int dx[] = {-1, 0, 1, 0}; // 上右下左
int dy[] = {0, 1, 0, -1}; // 上右下左
// bfs的特点决定它第一次找到的出口点,就是最短路径
PII q[M]; // 队列
void bfs(int sx, int sy) {
// 将起始点初始化到队列中
int hh = 0, tt = -1;
q[++tt] = {sx, sy};
// 初始化一个不存在的值,用以区分是不是走过了
// 这个memset很牛X的样子,可以把一个数组+结构体 中的数据全部初始化为-1!
memset(pre, -1, sizeof pre); // 利用pre数组来防止走回头路
pre[sx][sy] = {0, 0}; // 最后一个位置(n-1,n-1)只要设置一个非-1的值,就描述它走过了,防止重复走
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 4; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= n) continue; // 不越界
if (g[x][y]) continue; // 墙壁
if (~pre[x][y].x) continue; // 走过
// 可以走
q[++tt] = {x, y}; // 将可以走的点入队列
pre[x][y] = t; // 记录从t可以走到{x,y}
}
}
}
int main() {
cin >> n;
// 本题要求左上角是(0,0),下标不能从1开始
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> g[i][j];
// 因为每个节点都记录的是自己的上一步是谁,但没有记录自己的下一步是谁
// 所以,如果是正向搜索,从(0,0)出发,(0,0)不知道自己的下一步是哪个位置!
// 如果倒着搜索,从(n-1,n-1)出发,则遍历到的每个位置坐标(x,y),都知道自己的前序是谁,
// 一直到(0,0),(0,0)也就知道了自己的前序节点是哪个,这是一个通用的套路
bfs(n - 1, n - 1);
// 输出路径
PII start = {0, 0};
while (true) {
printf("%d %d\n", start.x, start.y);
if (start.x == n - 1 && start.y == n - 1) break;
start = pre[start.x][start.y];
}
return 0;
}
```
### 四、正序记录
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
// 边权为1的时候,可以使用bfs求最短路径
// 学习:bfs中如何记录路径,就是把bool st[N][N]修改为pair类型,记录前序是哪个点
// 这样,st就不光可以记录走没走过(没走过的话,值为空,走过的话有PII值),而且可以通过PII反推回走的路线
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 1010, M = N * N;
int n;
int g[N][N]; // 地图
PII q[M]; // 队列
PII pre[N][N]; // 前序坐标
int dx[] = {-1, 0, 1, 0}; // 上右下左
int dy[] = {0, 1, 0, -1}; // 上右下左
void bfs(int sx, int sy) {
// 将起始点初始化到队列中
int hh = 0, tt = -1;
q[++tt] = {sx, sy};
// 初始化一个不存在的值,用以区分是不是走过了
memset(pre, -1, sizeof pre);
pre[sx][sy] = {0, 0}; // 最后一个位置(n-1,n-1)只要设置一个非-1的值,就描述它走过了,防止重复走
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 4; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= n) continue; // 不越界
if (g[x][y]) continue; // 墙壁
if (~pre[x][y].x) continue; // 走过
// 可以走
q[++tt] = {x, y}; // 下一个要检查的坐标
pre[x][y] = t; // 从t可以走到{x,y}
}
}
}
int main() {
cin >> n;
// 本题要求左上角是(0,0),下标不能从1开始
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> g[i][j];
// 正向搜索
bfs(0, 0);
// 输出路径
vector<PII> path;
PII start = {n - 1, n - 1};
path.push_back(start);
while (true) {
if (start.x == 0 && start.y == 0) break;
start = pre[start.x][start.y];
path.push_back(start);
}
for (int i = path.size() - 1; i >= 0; i--) printf("%d %d\n", path[i].x, path[i].y);
return 0;
}
```

View File

@@ -0,0 +1,73 @@
#include <bits/stdc++.h>
using namespace std;
// 边权为1的时候,可以使用bfs求最短路径
// 学习:bfs中如何记录路径,就是把bool st[N][N]修改为pair类型,记录前序是哪个点
// 这样,st就不光可以记录走没走过(没走过的话,值为空,走过的话有PII值),而且可以通过PII反推回走的路线
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 1010, M = N * N;
int n;
int g[N][N]; // 地图,1是墙壁,0是可以走的路,4个方向可以走
PII pre[N][N];
// 前序坐标,每个位置,都需要记录它的前序来源坐标,
// 如果来源坐标还是默认的初始值-1,表示没有走过;如果来源坐标里有一个数对,就知道它是由某个位置走过来的,就是走过了
// 参数:x,y 坐标,值:前驱是哪个节点PII,相当于一个PII组成的链条,从最后一个出发,不断的找自己的前驱,就可以找到起点
// 配合一个 while(true)的循环,不断的向前找前驱,就可以走完最短路径的全程,至于是不是反了
// 背一下结论: 
// 倒着推:while(true)直接输出
// 正着推:需要配合vecotr<PII>进行一次暂存,然后再倒序输出即可
int dx[] = {-1, 0, 1, 0}; // 上右下左
int dy[] = {0, 1, 0, -1}; // 上右下左
// bfs的特点决定它第一次找到的出口点,就是最短路径
PII q[M]; // 队列
void bfs(int sx, int sy) {
// 将起始点初始化到队列中
int hh = 0, tt = -1;
q[++tt] = {sx, sy};
// 初始化一个不存在的值,用以区分是不是走过了
// 这个memset很牛X的样子,可以把一个数组+结构体 中的数据全部初始化为-1!
memset(pre, -1, sizeof pre); // 利用pre数组来防止走回头路
pre[sx][sy] = {0, 0}; // 最后一个位置(n-1,n-1)只要设置一个非-1的值,就描述它走过了,防止重复走
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 4; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= n) continue; // 不越界
if (g[x][y]) continue; // 墙壁
if (~pre[x][y].x) continue; // 走过
// 可以走
q[++tt] = {x, y}; // 将可以走的点入队列
pre[x][y] = t; // 记录从t可以走到{x,y}
}
}
}
int main() {
cin >> n;
// 本题要求左上角是(0,0),下标不能从1开始
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> g[i][j];
// 因为每个节点都记录的是自己的上一步是谁,但没有记录自己的下一步是谁
// 所以,如果是正向搜索,从(0,0)出发,(0,0)不知道自己的下一步是哪个位置!
// 如果倒着搜索,从(n-1,n-1)出发,则遍历到的每个位置坐标(x,y),都知道自己的前序是谁,
// 一直到(0,0),(0,0)也就知道了自己的前序节点是哪个,这是一个通用的套路
bfs(n - 1, n - 1);
// 输出路径
PII start = {0, 0};
while (true) {
printf("%d %d\n", start.x, start.y);
if (start.x == n - 1 && start.y == n - 1) break;
start = pre[start.x][start.y];
}
return 0;
}

View File

@@ -0,0 +1,63 @@
#include <bits/stdc++.h>
using namespace std;
// 边权为1的时候,可以使用bfs求最短路径
// 学习:bfs中如何记录路径,就是把bool st[N][N]修改为pair类型,记录前序是哪个点
// 这样,st就不光可以记录走没走过(没走过的话,值为空,走过的话有PII值),而且可以通过PII反推回走的路线
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 1010, M = N * N;
int n;
int g[N][N]; // 地图
PII q[M]; // 队列
PII pre[N][N]; // 前序坐标
int dx[] = {-1, 0, 1, 0}; // 上右下左
int dy[] = {0, 1, 0, -1}; // 上右下左
void bfs(int sx, int sy) {
// 将起始点初始化到队列中
int hh = 0, tt = -1;
q[++tt] = {sx, sy};
// 初始化一个不存在的值,用以区分是不是走过了
memset(pre, -1, sizeof pre);
pre[sx][sy] = {0, 0}; // 最后一个位置(n-1,n-1)只要设置一个非-1的值,就描述它走过了,防止重复走
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 4; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= n) continue; // 不越界
if (g[x][y]) continue; // 墙壁
if (~pre[x][y].x) continue; // 走过
// 可以走
q[++tt] = {x, y}; // 下一个要检查的坐标
pre[x][y] = t; // 从t可以走到{x,y}
}
}
}
int main() {
cin >> n;
// 本题要求左上角是(0,0),下标不能从1开始
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
cin >> g[i][j];
// 正向搜索
bfs(0, 0);
// 输出路径
vector<PII> path;
PII start = {n - 1, n - 1};
path.push_back(start);
while (true) {
if (start.x == 0 && start.y == 0) break;
start = pre[start.x][start.y];
path.push_back(start);
}
for (int i = path.size() - 1; i >= 0; i--) printf("%d %d\n", path[i].x, path[i].y);
return 0;
}

View File

@@ -0,0 +1,42 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;
int n, k;
int q[N];
int dist[N];
int Min = INF;
int bfs() {
//初始化距离数组-1
memset(dist, -1, sizeof dist);
int hh = 0, tt = -1;
q[++tt] = n; //加入起点n
dist[n] = 0; // n距离出发点0个长度
while (hh <= tt) {
int t = q[hh++];
if (t == k) return dist[k];
if (t + 1 < N && dist[t + 1] == -1) {
dist[t + 1] = dist[t] + 1;
q[++tt] = t + 1;
}
if (t - 1 >= 0 && dist[t - 1] == -1) {
dist[t - 1] = dist[t] + 1;
q[++tt] = t - 1;
}
if (t * 2 < N && dist[t * 2] == -1) {
dist[t * 2] = dist[t] + 1;
q[++tt] = t * 2;
}
}
return 0;
}
int main() {
//农夫起始位于点N,牛位于点K
cin >> n >> k;
printf("%d\n", bfs());
return 0;
}

View File

@@ -0,0 +1,83 @@
## [$AcWing$ $1100$. 抓住那头牛](https://www.acwing.com/problem/content/1102/)
### 一、题目描述
农夫知道一头牛的位置,想要抓住它。
农夫和牛都位于数轴上,农夫起始位于点 $N$,牛位于点 $K$。
农夫有两种移动方式:
从 $X$ 移动到 $X−1$ 或 $X+1$,每次移动花费一分钟
从 $X$ 移动到 $2∗X$,每次移动花费一分钟
假设牛没有意识到农夫的行动,站在原地不动。
农夫 **最少** 要花多少时间才能抓住牛?
**输入格式**
共一行,包含两个整数$N$和$K$。
**输出格式**
输出一个整数,表示抓到牛所花费的 **最少时间** 。
**数据范围**
$0≤N,K≤10^5$
**输入样例**:
```cpp {.line-numbers}
5 17
```
**输出样例**:
```cpp {.line-numbers}
4
```
### 二、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;
int n, k;
int q[N];
int dist[N];
int Min = INF;
int bfs() {
//初始化距离数组-1
memset(dist, -1, sizeof dist);
int hh = 0, tt = -1;
q[++tt] = n; //加入起点n
dist[n] = 0; // n距离出发点0个长度
while (hh <= tt) {
int t = q[hh++];
if (t == k) return dist[k];
if (t + 1 < N && dist[t + 1] == -1) {
dist[t + 1] = dist[t] + 1;
q[++tt] = t + 1;
}
if (t - 1 >= 0 && dist[t - 1] == -1) {
dist[t - 1] = dist[t] + 1;
q[++tt] = t - 1;
}
if (t * 2 < N && dist[t * 2] == -1) {
dist[t * 2] = dist[t] + 1;
q[++tt] = t * 2;
}
}
return 0;
}
int main() {
//农夫起始位于点N,牛位于点K
cin >> n >> k;
printf("%d\n", bfs());
return 0;
}
```
### 三、总结
蓝桥杯青少组初赛原题,同类题比如 [奇怪的电梯](https://www.luogu.com.cn/problem/P1135)

View File

@@ -0,0 +1,202 @@
## [$AcWing$ $1129$. 热浪【单源最短路】](https://www.acwing.com/problem/content/description/1131/)
### 一、题目描述
德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!!
他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品。
农夫$John$此时身先士卒地承担起向德克萨斯运送大量的营养冰凉的牛奶的重任,以减轻德克萨斯人忍受酷暑的痛苦。
$John$已经研究过可以把牛奶从威斯康星运送到德克萨斯州的路线。
这些路线包括 **起始点** 和 **终点** 一共有 $T$ 个城镇,为了方便标号为 $1$ 到 $T$。
除了起点和终点外的每个城镇都由 **双向道路** 连向至少两个其它的城镇。
每条道路有一个通过费用(包括油费,过路费等等)。
给定一个地图,包含 $C$ 条直接连接 $2$ 个城镇的道路。
每条道路由道路的起点 $R_s$,终点 $R_e$ 和花费 $C_i$ 组成。
求从起始的城镇 $T_s$ 到终点的城镇 $T_e$ **最小的总费用**。
**输入格式**
第一行: $4$ 个由空格隔开的整数: $T,C,T_s,T_e$;
第 $2$ 到第 $C+1$ 行: 第 $i+1$ 行描述第 $i$ 条道路,包含 $3$ 个由空格隔开的整数: $R_s,R_e,C_i$。
**输出格式**
一个单独的整数表示从 $T_s$ 到 $T_e$ 的最小总费用。
数据保证至少存在一条道路。
### 二、题目分析
**单源点**,**<font color='red'>正权图</font>**,求到终点的 **最短路径**
经典的 **单源最短路** 裸题,点数的范围是 $n=2500$,边数的范围是 $m=12400$
* $Dijkstra$ **堆优化版**
**时间复杂度** 为 $O(mlog_n)$,本题运算上界是 $1.36×10^5$
### 三、$Dijkstra$+堆优化
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 2510;
const int M = 6200 * 2 + 10;
typedef pair<int, int> PII;
int h[N], w[M], e[M], ne[M], idx;
bool st[N];
int dis[N];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, m, S, T;
int dijkstra() {
memset(dis, 0x3f, sizeof dis);
dis[S] = 0;
priority_queue<PII, vector<PII>, greater<PII>> q;
q.push({0, S});
while (q.size()) {
PII t = q.top();
q.pop();
int u = t.second;
if (st[u]) continue;
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (dis[v] > dis[u] + w[i]) {
dis[v] = dis[u] + w[i];
q.push({dis[v], v});
}
}
}
return dis[T];
}
int main() {
cin >> n >> m >> S >> T;
memset(h, -1, sizeof h);
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
printf("%d\n", dijkstra());
return 0;
}
```
### 七、$SPFA$ 【已死,有事烧纸~】
$SPFA$算法是$Bellman\_Ford$的一种 **队列改进**,减少了不必要的冗余计算;相比于$Dijkstra$算法的 **优点** 是可以用来 **在负权图上求最短路**,且平均情况下复杂度也 **较优**;
#### 算法思想
用一个队列来从源点开始维护,使得队列中的每个点都与它相连的点进行松弛操作;若松弛成功,则入队;否则开始下一个点的松弛;直到队列为空;
从上不难看出,其本质就是$bfs$的过程,核心部分就是 **松弛**;
和$bfs$不同的是,$bfs$时每个点都最多只入队一次,而$SPFA$算法中 **每个点可以多次入队**;也就是说,**一个点在改进其它的点后,本身有可能再次被其它的点改进,然后加入队列再次准备改进其它的点,如此反复迭代**......直到所有的点都无法再改进;此时,队列为空。
$SPFA$算法是一种求解单源最短路径的算法,也就是说,它可以求解某个点 $S$ 到图中其它所有的点的距离;所以我们用$dist[i]$来表示 $S$ 到 $i$ 的最短距离。另外,它还可以检查出 **负环**,**判断有无负环**:若某个点的入队次数超过 $V$ (代表顶点数)次,则**存在负环**,所以我们用$count[i]$ 来表示 $i$ 点进入队列的次数;
$SPFA$算法有两个优化算法 $SLF$ 和 $LLL$。
* $SLF$: $Small$ $Label$ $First$ 策略,设要加入的节点是$j$,队首元素为$i$,若$dist(j)<dist(i)$,则将$j$插入队首,否则插入队尾。
* $LLL$: $Large$ $Label$ $Last$ 策略,设队首元素为$i$,队列中所有$dist$值的平均值为$x$,若$dist(i)>x$则将$i$插入到队尾,查找下一元素,直到找到某一$i$使得$dist(i)<=x$,则将 $i$ 出队进行松弛操作。
$SLF$ 可使速度提高 $15 \sim 20\% $;$SLF + LLL$ 可提高约 $50\%$。 在实际的应用中$SPFA$的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的$Dijkstra$算法。
#### 如何看待 $SPFA$ 算法已死这种说法?
在非负边权的图中,随手卡 $SPFA$ 已是业界常识。在负边权的图中,不把 $SPFA$ 卡到最慢就设定时限是非常不负责任的行为,而卡到最慢就意味着 $SPFA$ 和传统 $Bellman$ $Ford$ 算法的时间效率类似,而后者的实现难度远低于前者。$SPFA$ 的受到怀疑和最终消亡,是 $OI$ 界水平普遍提高、命题规范完善和出题人的使命感和责任心增强的最好见证。
#### 观点
* 没有负权:$dijkstra$
* 有负权 卡$spfa$:写$spfa$ 最坏也是一个$bellman-ford$
* 随机图:$spfa$飞快
所以,可以写$spfa$,不要故意写$spfa$就可以了吧
坚定相信: **我写的这个是$bellman-ford$,只是某些图他跑的飞飞飞飞快**
**实现代码**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
/*
(spfa) O(m)
平均O(m),最坏O(nm) 一般会被卡到O(n*m)会被卡死
*/
const int N = 2510; // 端点数量
const int M = 6200 * 2 + 10; // 边的数量,记得别开太小,和N一样就等着挂吧
int n; // n个城镇
int m; // m条路径
int S; // 起点
int T; // 终点
int h[N], e[M], w[M], ne[M], idx; // 邻接表
int d[N]; // 最短距离数组
queue<int> q; // 队列
bool st[N]; // 是否使用过
// 邻接表模板
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// spfa模板
void spfa() {
// 将所有距离初始化为无穷大
memset(d, 0x3f, sizeof d);
// 出发点的距离清零
d[S] = 0;
q.push(S); // 出发点入队列
st[S] = true; // 出发点标识已使用
while (q.size()) {
int u = q.front();
q.pop();
st[u] = false;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (d[v] > d[u] + w[i]) {
d[v] = d[u] + w[i];
if (!st[v]) {
st[v] = true;
q.push(v);
}
}
}
}
}
int main() {
// 输入n个城镇,m条路径,S:起点,T:终点
cin >> n >> m >> S >> T;
// 初始化邻接表
memset(h, -1, sizeof h);
// 读入m条路径
for (int i = 0; i < m; i++) {
// 每条路径的起点,终点,花费
int a, b, c;
cin >> a >> b >> c;
// 无向边
add(a, b, c), add(b, a, c);
}
spfa();
// 输出终点T的单源最短距离
printf("%d\n", d[T]);
return 0;
}
```

View File

@@ -0,0 +1,68 @@
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
#define x first
#define y second
typedef pair<int, int> PII;
/*
不再是简单的4连通或8连通,而是按中国象棋的“日”的方式去走!
日:共8个方向
K:起点,H:终点,*:障碍, .:可以跳
就是一个BFS最短路径问题,关键在于如何跳“日”,其实本质还是dx8的参数配置问题了。
*/
const int N = 155, M = N * N;
int n, m;
char g[N][N];
PII q[M];
int dist[N][N]; // 距离数组
// 8个方向
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
int ans = INF;
void bfs() {
// 初始化距离数组为-1
memset(dist, -1, sizeof dist);
// 出发点坐标
int sx, sy;
// 找出发点
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (g[i][j] == 'K') sx = i, sy = j;
// 队列
int hh = 0, tt = -1;
q[++tt] = {sx, sy};
dist[sx][sy] = 0; // 标识走过,防止走回头路
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 8; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= m) continue;
if (g[x][y] == '*') continue; // 障碍的位置用 * 来标记
if (~dist[x][y]) continue; // 已经走过不能再走,防止走回头路
if (g[x][y] == 'H') { // 草的位置用 H 来标记。
ans = dist[t.x][t.y] + 1; // 多跳一步吃到草
return; // 吃到草就OK了
}
dist[x][y] = dist[t.x][t.y] + 1; // 多跳一步
q[++tt] = {x, y}; // 入队列继续扩展
}
}
}
int main() {
cin >> m >> n; // 居然先输入列数,再输入行数,bt!
// 这个输入太讨厌了,居然只是按行读入,每次读取一行,那么cin读到的就是一个字符串,目标是一个字符数组的话,就直接进入字符数组了~,这与以前的题目不同,要注意
for (int i = 0; i < n; i++) cin >> g[i]; // 题意下标从0开始,注意~
// 找最短路
bfs();
// 输出
printf("%d\n", ans);
return 0;
}

View File

@@ -0,0 +1,160 @@
## [$AcWing$ $188$. 武士风度的牛](https://www.acwing.com/problem/content/190/)
### 一、题目描述
农民 $John$ 有很多牛,他想交易其中一头被 $Don$ 称为 $The$ $Knight$ 的牛。
这头牛有一个独一无二的超能力,在农场里像 $Knight$ 一样地跳(就是我们熟悉的象棋中 **马的走法** )。
虽然这头神奇的牛不能跳到树上和石头上,但是它可以在牧场上随意跳,我们把牧场用一个 $x,y$ 的坐标图来表示。
这头神奇的牛像其它牛一样喜欢吃草,给你一张地图,上面标注了 $The$ $Knight$ 的开始位置,树、灌木、石头以及其它障碍的位置,除此之外还有一捆草。
现在你的任务是,确定 $The$ $Knight$ 要想吃到草,**至少需要跳多少次**。
$The$ $Knight$ 的位置用 $K$ 来标记,障碍的位置用 $*$ 来标记,草的位置用 $H$ 来标记。
这里有一个地图的例子:
```c++
11 | . . . . . . . . . .
10 | . . . . * . . . . .
9 | . . . . . . . . . .
8 | . . . * . * . . . .
7 | . . . . . . . * . .
6 | . . * . . * . . . H
5 | * . . . . . . . . .
4 | . . . * . . . * . .
3 | . K . . . . . . . .
2 | . . . * . . . . . *
1 | . . * . . . . * . .
0 ----------------------
1
0 1 2 3 4 5 6 7 8 9 0
```
$The$ $Knight$ 可以按照下图中的 $A,B,C,D…$ 这条路径用 $5$ 次跳到草的地方(有可能其它路线的长度也是 $5$):
```c++
11 | . . . . . . . . . .
10 | . . . . * . . . . .
9 | . . . . . . . . . .
8 | . . . * . * . . . .
7 | . . . . . . . * . .
6 | . . * . . * . . . F<
5 | * . B . . . . . . .
4 | . . . * C . . * E .
3 | .>A . . . . D . . .
2 | . . . * . . . . . *
1 | . . * . . . . * . .
0 ----------------------
1
0 1 2 3 4 5 6 7 8 9 0
```
**注意**: 数据保证一定有解。
**输入格式**
第 $1$ 行: 两个数,表示农场的列数 $C$ 和行数 $R$。
第 $2..R+1$ 行: 每行一个由 $C$ 个字符组成的字符串,共同描绘出牧场地图。
**输出格式**
一个整数,表示跳跃的最小次数。
**数据范围**
$1≤R,C≤150$
**输入样例**:
```cpp {.line-numbers}
10 11
..........
....*.....
..........
...*.*....
.......*..
..*..*...H
*.........
...*...*..
.K........
...*.....*
..*....*..
```
**输出样例**:
```cpp {.line-numbers}
5
```
### 二、坐标分析
<center><img src='https://img2022.cnblogs.com/blog/8562/202203/8562-20220302170939361-294790510.png'></center>
### 三、实现代码
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
#define x first
#define y second
typedef pair<int, int> PII;
/*
不再是简单的4连通或8连通,而是按中国象棋的“日”的方式去走!
日:共8个方向
K:起点,H:终点,*:障碍, .:可以跳
就是一个BFS最短路径问题,关键在于如何跳“日”,其实本质还是dx8的参数配置问题了。
*/
const int N = 155, M = N * N;
int n, m;
char g[N][N];
PII q[M];
int dist[N][N]; // 距离数组
// 8个方向
int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2};
int dy[] = {1, 2, 2, 1, -1, -2, -2, -1};
int ans = INF;
void bfs() {
// 初始化距离数组为-1
memset(dist, -1, sizeof dist);
// 出发点坐标
int sx, sy;
// 找出发点
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
if (g[i][j] == 'K') sx = i, sy = j;
// 队列
int hh = 0, tt = -1;
q[++tt] = {sx, sy};
dist[sx][sy] = 0; // 标识走过,防止走回头路
while (hh <= tt) {
PII t = q[hh++];
for (int i = 0; i < 8; i++) {
int x = t.x + dx[i], y = t.y + dy[i];
if (x < 0 || x >= n || y < 0 || y >= m) continue;
if (g[x][y] == '*') continue; // 障碍的位置用 * 来标记
if (~dist[x][y]) continue; // 已经走过不能再走,防止走回头路
if (g[x][y] == 'H') { // 草的位置用 H 来标记。
ans = dist[t.x][t.y] + 1; // 多跳一步吃到草
return; // 吃到草就OK了
}
dist[x][y] = dist[t.x][t.y] + 1; // 多跳一步
q[++tt] = {x, y}; // 入队列继续扩展
}
}
}
int main() {
cin >> m >> n; // 居然先输入列数,再输入行数,bt!
// 这个输入太讨厌了,居然只是按行读入,每次读取一行,那么cin读到的就是一个字符串,目标是一个字符数组的话,就直接进入字符数组了~,这与以前的题目不同,要注意
for (int i = 0; i < n; i++) cin >> g[i]; // 题意下标从0开始,注意~
// 找最短路
bfs();
// 输出
printf("%d\n", ans);
return 0;
}
```

Binary file not shown.

View File

@@ -0,0 +1,49 @@
## $CSP-J2$ 现场考试一定要注意的事项
### 一、需要上来先打一个空模板,测试一下万能头是否可用
```c++
#include <bits/stdc++.h>
using namespace std;
int main() {
//文件输入
freopen("Candy.in", "r", stdin);
//文件输出
freopen("Candy.out", "w", stdout);
return 0;
}
```
* 其它几题复制使用
* 如果题目不是按以前练习的在开发环境中完成编码后,复制到页面提交的形式,而是要求 <font color='red' size=4><b>
* 读取 $xx.in$
* 提交 $xx.out$</b></font>
则需要按下面的样子完成编码,上面的代码需要同学们背诵,否则挂零。
### 二、如果无法使用万能头文件
```c++
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//如果需要队列
//#include <queue>
//如果需要Hash表
//#include <unordered_map>
using namespace std;
int main() {
//文件输入
freopen("Candy.in", "r", stdin);
//文件输出
freopen("Candy.out", "w", stdout);
return 0;
}
```

View File

@@ -0,0 +1,278 @@
## 分层图最短路
[P4568 [JLOI2011]飞行路线](https://www.luogu.org/problemnew/show/P4568)
### 一、分层图概念
**分层图最短路** :在可以进行分层图的图上解决最短路问题
**分层图**:理解为有 **多个平行的图**
**模型**:在一个正常的图上可以进行 $k$ 次决策,对于每次决策,不影响图的结构,只影响目前的 **状态** 或 **代价**。一般将 **决策前的状态** 和 **决策后的状态** 之间 **连接一条权值为决策代价的边**,表示付出该代价后就可以转换状态了
### 二、建图方式
有两种方法解决分层图最短路问题:
* 建图时 **直接建成$k+1$层**
* **多开一维** 记录分层信息
#### 1、建图时直接建成$k+1$层
我们建$k+1$层图。然后有边的两个点,多建一条到下一层边权为$0$的单向边,如果走了这条边就表示用了一次机会。
有$N$个点时:
<!-- 让表格居中显示的风格 -->
<style>
.center
{
width: auto;
display: table;
margin-left: auto;
margin-right: auto;
}
</style>
<div class="center">
| 层数 | 开始 | 结束 |
| ---- | ---- | ---- |
| 第一层 | $1$ | $n$ |
| 第二层 | $n+1$ |$2n$ |
| 第三层 | $2n+1$|$3n$|
| ...| ...|...|
| 第$i$层| $(i-1)*n+1$|$i*n$|
| 第$i+1$层| $i*n+1$|$(i+1)*n$|
</div>
原始图要占一层,每经过一个条件变化,就到达一层,共$k$个条件,所以,要建$K+1$层图,数组要开到$n*(k+1)$,点的个数也为$n*(k+1)$
举个栗子:
```c++
n = 4,m = 3,k = 2
0 1 100
1 2 100
2 3 100
```
* $4$个节点,$3$条边,起点、终点、边权为上面的三组数据
* $2$有两条边可以免费,求第$3$长的边最短是多少
建成图之后大概是这样的:
![20221022112759](https://cdn.jsdelivr.net/gh/littlehb/ShaoHuiLin/20221022112759.png)
对于上面的数据:答案就是$3$,$3+n$,$3+2n$ 中的 **最小值**
**注意**
由于分层图的 **空间复杂度** 及 **时间复杂度较高**(特别是空间复杂度),故在分析时 **一定要计算好时间及空间**:
**边数**
* $m*(k+1$),无向图则需$*2$(可以理解为$2$个点互连的有向图)
* 考虑到每层图之间存在多条权值为$0$的边,一层最多有$m$条边,共$k+1$层(其实这里存在 **楼梯** 的是$k$层,多算一点防止$RE$),考虑无向图,$*2$
$$\large M=m*(k+1)*2+m*(k+1)*2+10$$
本题就是:$M=5e4*11*2+ 5e4*11*2+ 10$
千万不要因为 **空间没开够** 或 **爆空间** 而导致$RE$!!!
```c++
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 * 2 * 11 + 10; //节点数:1e4,无向图,1e4*2,共k+1(k<=10)层:1e4*2*11
const int M = 5e4 * 11 * 3 + 10; //边数
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
int dist[N], st[N];
//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, m, s, t, k;
void dijkstra() {
priority_queue<PII, vector<PII>, greater<PII>> q;
dist[s] = 0;
q.push({0, s});
while (q.size()) {
int u = q.top().second;
q.pop();
if (st[u]) continue;
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (st[j]) continue;
if (dist[j] > dist[u] + w[i]) {
dist[j] = dist[u] + w[i];
q.push({dist[j], j});
}
}
}
}
int main() {
//多组测试数据
while (~scanf("%d%d%d", &n, &m, &k)) {
//初始化
memset(h, -1, sizeof(h));
idx = 0;
memset(dist, 0x3f, sizeof(dist));
memset(st, false, sizeof(st));
scanf("%d%d", &s, &t); //起点与终点
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
//分层图建图
for (int i = 0; i <= k; i++) { //创建k+1层分层图
add(a + i * n, b + i * n, c), add(b + i * n, a + i * n, c); //无向图
if (i < k) //从第0层开始,到k-1层结束,都需要向下一层建立通道
add(a + i * n, b + (i + 1) * n, 0), add(b + i * n, a + (i + 1) * n, 0);
}
}
//一遍最短路
dijkstra();
// k+1个层中,都去找t的最短路径,再取最小值,就是答案
int ans = INF;
for (int i = 0; i <= k; i++)
ans = min(ans, dist[t + i * n]);
printf("%d\n", ans);
}
return 0;
}
```
#### 2、多开一维记录分层信息
我们把$dist$数组和$st$数组 **多开一维** 记录$k$次机会的信息
$dist[i][j]$ 代表到达 $j$ 用了 $i$ 次免费机会的 **最小花费**
$st[i][j]$ 代表到达 $j$ 用了 $i$ 次 免费机会的情况 **是否出现过**
更新步骤:
* **先更新同层之间**(即花费免费机会相同)的最短路
`dist[r][j] = min(dist[r][j],dist[r][u] + w[i]);`
* **更新从该层到下一层**(即再花费一次免费机会)的最短路。
`dist[r+1][j] = min(dist[r+1][j],dist[r][u]);`
对于数据:
```c++
n = 4,m = 3,k = 2
0 1 100
1 2 100
2 3 100
```
建成图之后大概是这样的:
![20221022114046](https://cdn.jsdelivr.net/gh/littlehb/ShaoHuiLin/20221022114046.png)
```c++
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
const int N = 1e4 * 2 * 11 + 10; //节点数:1e4,无向图,1e4*2,共k+1(k<=10)层:1e4*2*11
const int M = N << 1; //边数
//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int dist[15][N], st[15][N];
int n, m, s, t, k;
void dijkstra() {
priority_queue<PII, vector<PII>, greater<PII>> q;
dist[0][s] = 0; //第零层,起点s
q.push({0, s});
while (q.size()) {
int u = q.top().second;
q.pop();
int r = u / n; //行
u %= n; //列
if (st[r][u]) continue;
st[r][u] = true;
//更新同行,不使用免费机会
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (st[r][j]) continue;
if (dist[r][j] > dist[r][u] + w[i]) {
dist[r][j] = dist[r][u] + w[i];
q.push({dist[r][j], j + r * n});
}
}
//更新下一行,使用免费机会
if (r < k) {
//出发点u,目标点:下一行的j位置
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (st[r + 1][j]) continue; //下一行,j列走过吗?
if (dist[r + 1][j] > dist[r][u]) { //从r,u直接通过零成本过来 (r+1,j)
dist[r + 1][j] = dist[r][u];
q.push({dist[r + 1][j], j + (r + 1) * n});
}
}
}
}
}
int main() {
while (~scanf("%d%d%d", &n, &m, &k)) {
memset(h, -1, sizeof(h));
memset(dist, 0x3f, sizeof(dist));
memset(st, false, sizeof(st));
idx = 0;
scanf("%d%d", &s, &t);
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dijkstra();
int ans = INF;
for (int i = 0; i <= k; i++)
ans = min(ans, dist[i][t]);
printf("%d\n", ans);
}
return 0;
}
```
### 三、选择哪种方法
具体选择哪一种方法,看数据范围吧:
* 直接建成$k+1$层
一次性建全$k+1$层,如果超过题目上限$128MB$,那么就只能使用第二种办法。
$$\large M=m*(k+1)*2+m*(k+1)*2 \approx m*4*k $$
比如本题:$m=5e4,k=10$,就是$5e4*4*10=2e6$
$2e6 *4byte=8e6 ~ byte = 7812kb = 7.6mb$
完全没有问题。
* 多开一维记录分层信息
这种办法由于只创建了一层节点的边关系,会小$k$倍的内存,同时,由于优先队列是一边进一边出的,所以内存可以控制。

View File

@@ -0,0 +1,74 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 * 2 * 11 + 10; //节点数:1e4,无向图,1e4*2,共k+1(k<=10)层:1e4*2*11
const int M = 5e4 * 11 * 3 + 10; //边数
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
int dist[N], st[N];
//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, m, s, t, k;
void dijkstra() {
priority_queue<PII, vector<PII>, greater<PII>> q;
dist[s] = 0;
q.push({0, s});
while (q.size()) {
int u = q.top().second;
q.pop();
if (st[u]) continue;
st[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (st[j]) continue;
if (dist[j] > dist[u] + w[i]) {
dist[j] = dist[u] + w[i];
q.push({dist[j], j});
}
}
}
}
int main() {
//多组测试数据
while (~scanf("%d%d%d", &n, &m, &k)) {
//初始化
memset(h, -1, sizeof(h));
idx = 0;
memset(dist, 0x3f, sizeof(dist));
memset(st, false, sizeof(st));
scanf("%d%d", &s, &t); //起点与终点
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
//分层图建图
for (int i = 0; i <= k; i++) { //创建k+1层分层图
add(a + i * n, b + i * n, c), add(b + i * n, a + i * n, c); //无向图
if (i < k) //从第0层开始,到k-1层结束,都需要向下一层建立通道
add(a + i * n, b + (i + 1) * n, 0), add(b + i * n, a + (i + 1) * n, 0);
}
}
//一遍最短路
dijkstra();
// k+1个层中,都去找t的最短路径,再取最小值,就是答案
int ans = INF;
for (int i = 0; i <= k; i++)
ans = min(ans, dist[t + i * n]);
printf("%d\n", ans);
}
return 0;
}

View File

@@ -0,0 +1,77 @@
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> PII;
const int N = 1e4 * 2 * 11 + 10; //节点数:1e4,无向图,1e4*2,共k+1(k<=10)层:1e4*2*11
const int M = N << 1; //边数
//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int dist[15][N], st[15][N];
int n, m, s, t, k;
void dijkstra() {
priority_queue<PII, vector<PII>, greater<PII>> q;
dist[0][s] = 0; //第零层,起点s
q.push({0, s});
while (q.size()) {
int u = q.top().second;
q.pop();
int r = u / n; //行
u %= n; //列
if (st[r][u]) continue;
st[r][u] = true;
//更新同行,不使用免费机会
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (st[r][j]) continue;
if (dist[r][j] > dist[r][u] + w[i]) {
dist[r][j] = dist[r][u] + w[i];
q.push({dist[r][j], j + r * n});
}
}
//更新下一行,使用免费机会
if (r < k) {
//出发点u,目标点:下一行的j位置
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (st[r + 1][j]) continue; //下一行,j列走过吗?
if (dist[r + 1][j] > dist[r][u]) { //从r,u直接通过零成本过来 (r+1,j)
dist[r + 1][j] = dist[r][u];
q.push({dist[r + 1][j], j + (r + 1) * n});
}
}
}
}
}
int main() {
while (~scanf("%d%d%d", &n, &m, &k)) {
memset(h, -1, sizeof(h));
memset(dist, 0x3f, sizeof(dist));
memset(st, false, sizeof(st));
idx = 0;
scanf("%d%d", &s, &t);
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dijkstra();
int ans = INF;
for (int i = 0; i <= k; i++)
ans = min(ans, dist[i][t]);
printf("%d\n", ans);
}
return 0;
}

View File

@@ -0,0 +1,40 @@
## 【C语言语法】 POJ上奇奇怪怪的Compile error
>收集中,因为老在$POJ$上莫名奇妙地$CE$,所以记录一下出现过的错误
### 1.不能用万能头文件`<bits/stdc++.h>`
**懒癌克星**
### 2.不支持以{}的形式为结构体赋值:
```c++
typedef struct{
int x,y,z;
int step;
}pos;
pos S;
queue <pos> q;
//以下三种方式都不行
S={i,j,k,0};
S=pos{i,j,k,0};
q.push({new_x,new_y,new_z,new_step});
```
### 3.需要加`<string>`头文件才能实现对`string`类型的某些操作
$string$类型判断两个字符串是否相等可以直接用$==$
但是在$poj$上没加`<string>`头文件会$CE$,我自己的编译器上不会,不知道是什么原因
### 4.不能以变量作为数组的`size`
```c++
while(cin>>N&&N){
string s[N+1];
}
```
显示:
<center><img src='https://img-blog.csdnimg.cn/20201118194110805.png'></center>
### 5.不能用`distance`作为函数名
貌似与`xutility`中的一个函数重名了?

View File

@@ -0,0 +1,56 @@
## $spfa$和$bfs$的区别
$spfa$在形式上和$bfs$非常类似,不同的是$bfs$中一个点出了队列就不可能重新进入队列,但是$spfa$中一个点可能在出队列之后再次被放入队列,也就是一个点松弛过其它的点之后,过了一段时间可能本身被松弛,于是要再次用来松弛其它的点,这样反复迭代下去。
### 一、$bfs$算法模板
```c++
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size()){
int u = q.front();
q.pop();
for (int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if (!st[j]){
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
```
### 二、$spfa$算法模板
```c++
int spfa(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true;
while (q.size()){
auto u = q.front();
q.pop();
st[u] = false;
for (int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if (dist[j] > dist[u] + w[i]){
dist[j] = dist[u] + w[i];
// 如果队列中已存在j,则不需要将j重复插入
if (!st[j]){
q.push(j);
st[j] = true;
}
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
```
在$spfa$中一个点的距离 **可能被多次更新**,因为有权值为 **负数** 的边,而$Dijktra$,$bfs$所有边的 **权值都非负**,且每次更新其他点的距离的时候,**都取的是当前未确定和起点距离的点中距离最小的点**,所以当有其他路径通向这个点的时候不管是一条还是几条边距离一定都大于你选的这个边的距离,所以此时这个点到起点的距离就已经被确定不会改变了,而$spfa$允许存在负边,可能存在一条路径第一段权值较大,但是后几段距离是负数,就会更新$dist$了,所以在$spfa$算法中,在出队后将$st[t]$标记为$false$,使得以后还可以入队来更新路径。

View File

@@ -0,0 +1 @@
8 1

View File

@@ -0,0 +1 @@
16

View File

@@ -0,0 +1,18 @@
// #include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
using namespace std;
int main() {
freopen("Test.in", "r", stdin);
freopen("Test.out", "w", stdout);
int a, b;
cin >> a >> b;
cout << a + b << endl;
return 0;
}

View File

@@ -0,0 +1,17 @@
#include <bits/stdc++.h>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <vector>
#include <map>
#include <queue>
using namespace std;
int main() {
freopen("Test2.in", "r", stdin);
freopen("Test2.out", "w", stdout);
int a, b;
cin >> a >> b;
printf("%d", a + b);
return 0;
}

View File

@@ -0,0 +1 @@
5 9

View File

@@ -0,0 +1 @@
14

View File

@@ -0,0 +1,212 @@
## 关于优先队列$priority\_queue$大小根堆、重载操作符的说明
[感谢原作者](https://www.cnblogs.com/chenleideblog/p/12745271.html)
### 一、关于$priority\_queue$的说明
#### 内部实现
`priority_queue`默认情况下,以$vector$为底层容器,加上$heap$(默认$max-heap$) 处理规则;**形成大根堆**。
$priority\_queue$被归为 $container$ $adapter$,也就是对 $container$ 进行封装一层。
`priority_queue`操作规则上是 $queue$,只允许在尾部加入元素,并从首部取出元素;只不过内部元素具有优先级,优先级高者先出。
`priority_queue`的所有元素进出具有一定规则,所以 **不提供遍历功能**,也不提供迭代器。
#### 疑惑产生
下面为`priority_queue`的使用规则,第一个传入了类型,第二个为容器类型,第三个为**比较函数**。
```c++
template<
class T,
class Container = std::vector<T>,
class Compare = std::less<typename Container::value_type> //comp默认为less
> class priority_queue;
```
疑惑关键就在于比较函数。
`priority_queue`默认形成大根堆,而传入的$comp$默认为$less$。
#### 为何传入一个可将序列顺序调为有小到大的函数,建成的堆反而是大顶堆呢?
不知你们有没有这种感觉?直觉上认为传入$less$,建成小顶堆,而传入$greater$,建成大顶堆。
#### 源码解析
`std::less()`源码:若`__x < __y`,则返回$true$,顺序不变,否则,顺序发生变化。这个函数名含义与实现效果相一致。
```c++
struct less : public binary_function<_Tp, _Tp, bool>
{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x < __y; }
};
```
`make_heap`中调用的`__push_heap`源码:
```c++
__push_heap(_RandomAccessIterator __first, _Distance __holeIndex,
_Distance __topIndex, _Tp __value, _Compare __comp)
//__holeIndex 新添加节点的索引,即叫做孔洞
//__topIndex 顶端索引
//__value 新添加节点的值
//__comp 比较函数,传入为less
{
_Distance __parent = (__holeIndex - 1) / 2; //找到新节点父节点索引
while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value)) {
//若孔洞没有到达最顶端 && 父节点的值小于新添加节点的值,则要进行下列操作
//less中,左边比右边小则返回true,与less愿意相同
*(__first + __holeIndex) = *(__first + __parent); //将父节点的值放入孔洞
__holeIndex = __parent; //孔洞的索引编程了原来父节点的索引,往上移动了
__parent = (__holeIndex - 1) / 2; //那么孔洞的父节点又要继续往上找
}
*(__first + __holeIndex) = __value; //将新添加节点的值放在找到的位置(孔洞)
}
```
经过上面源码分析,传入的$comp$就是为了在比较 **孔洞节点** 与 **父节点** 的大小,若返回$true$, **才会进行交换操作** 。
所以传入$less$,返回$true$是因为 **父节点比孔洞节点小**,所以要进行交换,则将大的值移动到前面,所以建成的堆为 **大顶堆**。
而传入$greater$,返回$true$是因为 **父节点** 比 **孔洞节点大**,所以进行交换,则将大的值移动到了后面,所以建成的堆为 **小顶堆**。
所以,就明白了为什么传入$less$反而形成了大根堆,而传入$greater$则形成了小根堆。
### 二、如何使用
```c++
#include <queue>
using namespace std;
​
priority_queue<int> que; //默认定义了最大堆,等同于将第三个参数使用less<int>
priority_queue<int, vector<int>, less<int>> que; //定义大根堆
​
priority_queue<int, vector<int>, greater<int>> que; //定义小根堆,VS下需要加入头文件#include<functional>
​
//测试 priority_queue<int> que;
que.push(3);
que.push(5);
que.push(4);
cout << que.top() << endl; //5
​
//测试 priority_queue<int, vector<int>, less<int>> que;
que.push(3);
que.push(5);
que.push(4);
cout << que.top() << endl; //5
​
//测试 priority_queue<int, vector<int>, greater<int>> que;
que.push(3);
que.push(5);
que.push(4);
cout << que.top() << endl; //3
```
### 三、关于自定义优先级
上面给出了在处理整型等基本数据类型时直接出入$less$或者$greater$既可以建立相应的大顶堆或者小顶堆。若是处理结构体呢?如何定义优先级呢?
#### 普通数据类型
基本数据类型的比较函数可以直接使用`less<int>`或者`greater<int>`可以满足建立大根堆或者小根堆。
#### 结构体
对于结构体而言,将结构体放入优先队列中,比较函数需要建立在针对结构体的具体成员。
**自定义优先级的四种方法:**
* <以成员函数重载
```c++
struct Node { //我们将Node节点放入优先队列中希望以value进行比较
Node(int _id, int _value) : id(_id), value(_value){}
int id;
int value;
};
​
//大根堆
bool operator < (const Node& a, const Node& b){
return a.value < b.value; //将value的值由大到小排列,形成Node的大根堆
}
​
int main() {
struct Node node1(1, 5);
struct Node node2(2, 3);
struct Node node3(3, 4);
priority_queue<Node> que;
que.push(node1);
que.push(node2);
que.push(node3);
cout << que.top().value << endl; //5
}
//小根堆
bool operator < (const Node& a, const Node& b)
{
return a.value > b.value; //将value的值由小到大排列,形成Node的小根堆
}
​
cout << que.top().value << endl; //3
```
我试了 `bool operator > ()`,结果会报二进制“<”: 没有找到接受`const Node`类型的左操作数的运算符(或没有可接受的转换) 错误,我想原因应该是这样吧:`priority_queue`中默认的比较函数为`less`,`less`函数中只用到了 `{ return __x < __y; }`,所以重载中若只重载了`>`,函数找不到`<`,所以会出现错误。
```c++
struct less : public binary_function<_Tp, _Tp, bool>{
_GLIBCXX14_CONSTEXPR
bool
operator()(const _Tp& __x, const _Tp& __y) const
{ return __x < __y; }
};
```
* 自定义比较函数
```c++
struct cmp{​
bool operator ()(const Node& a, const Node& b){
return a.value < b.value;//将value的值由大到小排列,形成Node的大根堆
}
};
priority_queue<Node, vector<Node>, cmp>q;
cout << que.top().value << endl; //5
struct cmp{​
bool operator ()(const Node& a, const Node& b){
return a.value > b.value;//将value的值由小到大排列,形成Node的小根堆
}
};
priority_queue<Node, vector<Node>, cmp>q;
cout << que.top().value << endl; //3
```
上述在传入用户自定义的比较函数,那么在建堆过程中使用的`comp`函数即为我们自定义的`cmp`,这样分析同上。
* < 以类成员函数重载
```c++
struct Node { //我们将Node节点放入优先队列中希望以value进行比较
Node(int _id, int _value) : id(_id), value(_value){}
int id;
int value;
//大根堆
bool operator < (const Node& b) const //注意,此处若没有const则会报错
{
return value < b.value; //将value的值由大到小排列,形成Node的大根堆
}
};
cout << que.top().value << endl; //5
```
* <以友元函数重载 【这个和竞赛关系不大,暂时不用考虑】
```c++
struct Node{
int id;
int value;
friend bool operator<(const Node& a,const Node& b){
return a.value<b.value; //按value从大到小排列
}
};
priority_queue<Node>q;
```