Day62--图论--97. 小明逛公园(卡码网),127. 骑士的攻击(卡码网)
97. 小明逛公园(卡码网)
Floyd 算法对边的权值正负没有要求,都可以处理。
方法:Floyd算法(三维版本)
思路:
动态规划五步曲:
- 确定dp数组含义:
grid[i][j][k] = m
表示 节点i 到 节点j 以k节点为中间节点的最短距离为m。(k取值自[1,k]) - 递推公式:
- 节点i 到 节点j 的最短路径经过节点k
- 先从i走到k,再从k走到 j :
grid[i][j][k] = grid[i][k][k - 1] + grid[k][j][k - 1]
(这里可以理解为"蹭了一下"。从全局来看,这条路是"经过了k"的。但是分成两段来看,从i到k,是不经过k的,从k到j,也是不经过k的,所以取k-1)
- 先从i走到k,再从k走到 j :
- 节点i 到 节点j 的最短路径不经过节点k
- 不走k:
grid[i][j][k] = grid[i][j][k - 1]
- 不走k:
- 节点i 到 节点j 的最短路径经过节点k
- 初始化:
- 遍历顺序:从递推公式看,k 依赖于 k - 1, i 和j 的到 并不依赖与 i - 1 或者 j - 1 。或者可以这么理解,i和j只是图的遍历,而k才是动态规划里面的i的遍历。所以k要在最外层for。
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][][] graph = new int[n + 1][n + 1][n + 1];
// 初始化为不可达状态
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= n; k++) {
// 自身到自身的距离为0
if (i == j) {
graph[i][j][k] = 0;
} else {
graph[i][j][k] = 10005;
}
}
}
}
for (int i = 0; i < m; i++) {
int x = in.nextInt();
int y = in.nextInt();
int val = in.nextInt();
// 双向图
graph[x][y][0] = val;
graph[y][x][0] = val;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
graph[i][j][k] =
Math.min(graph[i][j][k - 1], graph[i][k][k - 1] + graph[k][j][k - 1]);
}
}
}
int count = in.nextInt();
while (count-- > 0) {
int start = in.nextInt();
int end = in.nextInt();
int res = graph[start][end][n];
System.out.println(res == 10005 ? -1 : res);
}
}
}
方法:Floyd算法(二维版本)
思路:
可以看到k只依赖于k-1,与k-2,k-3等无关,所以只需要两个变量就可以了。空间上,k这一维度可以灭掉。
写完这个版本之后,观察代码,其实这就是一道很简单的动态规划题,只不过维度比普通的动态规划要多了一维,看起来恐怖了而已。
对于graph[i][j]
怎么理解?
其实就是i到j怎么走?观察上层:就从i走到k,从k走到j会快一点吗?不会的话,按照原来的规划走。
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int m = in.nextInt();
int[][] graph = new int[n + 1][n + 1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++) {
// 自身到自身的距离为0
if (i == j) {
graph[i][j] = 0;
} else {
graph[i][j] = 10005;
}
}
}
for (int i = 0; i < m; i++) {
int x = in.nextInt();
int y = in.nextInt();
int val = in.nextInt();
// 双向图
graph[x][y] = val;
graph[y][x] = val;
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
// 这里其实隐藏了一个[k-1],也就是动态规划里面的上一层。
// i到j怎么走?观察上层:就从i走到k,从k走到j会快一点吗?不会的话,按照原来的规划走。
graph[i][j] = Math.min(graph[i][j], graph[i][k] + graph[k][j]);
}
}
}
int count = in.nextInt();
while (count-- > 0) {
int start = in.nextInt();
int end = in.nextInt();
int res = graph[start][end];
System.out.println(res == 10005 ? -1 : res);
}
}
}
127. 骑士的攻击(卡码网)
方法:A star算法
思路:
第一轮刷题先跳过了。远超能理解的程度。
java
import java.util.PriorityQueue;
import java.util.Scanner;
public class Main {
static int[][] moves = new int[1001][1001];
static int[][] dir = {{-2, -1}, {-2, 1}, {-1, 2}, {1, 2},
{2, 1}, {2, -1}, {1, -2}, {-1, -2}};
static int b1, b2;
// F = G + H
// G = 从起点到该节点路径消耗
// H = 该节点到终点的预估消耗
static class Knight implements Comparable<Knight> {
int x, y;
int g, h, f;
@Override
public int compareTo(Knight k) { // 重载比较器,实现最小堆(按f值从小到大)
return Integer.compare(this.f, k.f);
}
}
static PriorityQueue<Knight> que = new PriorityQueue<>();
static int Heuristic(Knight k) { // 欧拉距离
return (k.x - b1) * (k.x - b1) + (k.y - b2) * (k.y - b2); // 统一不开根号,这样可以提高精度
}
static void astar(Knight k) {
Knight cur, next;
que.add(k);
moves[k.x][k.y] = 1; // 修正:起点需要标记为已访问,避免重复处理
while (!que.isEmpty()) {
cur = que.poll();
if (cur.x == b1 && cur.y == b2)
break;
for (int i = 0; i < 8; i++) {
next = new Knight();
next.x = cur.x + dir[i][0];
next.y = cur.y + dir[i][1];
if (next.x < 1 || next.x > 1000 || next.y < 1 || next.y > 1000)
continue;
if (moves[next.x][next.y] == 0) {
moves[next.x][next.y] = moves[cur.x][cur.y] + 1;
// 开始计算F
next.g = cur.g + 5; // 统一不开根号,这样可以提高精度,马走日,1 * 1 + 2 * 2 = 5
next.h = Heuristic(next);
next.f = next.g + next.h;
que.add(next);
}
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
while (n-- > 0) {
int a1 = scanner.nextInt();
int a2 = scanner.nextInt();
b1 = scanner.nextInt();
b2 = scanner.nextInt();
// 重置moves数组
for (int i = 0; i < 1001; i++) {
for (int j = 0; j < 1001; j++) {
moves[i][j] = 0;
}
}
// 清空队列(处理多组测试用例)
que.clear();
Knight start = new Knight();
start.x = a1;
start.y = a2;
start.g = 0;
start.h = Heuristic(start);
start.f = start.g + start.h;
astar(start);
// 输出结果时减1,因为我们给起点加了1
System.out.println(moves[b1][b2] - 1);
}
scanner.close();
}
}