Day62--图论--97. 小明逛公园(卡码网),127. 骑士的攻击(卡码网)
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。
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++) {// 自身到自身的距离为0if (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会快一点吗?不会的话,按照原来的规划走。
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++) {// 自身到自身的距离为0if (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算法
思路:
第一轮刷题先跳过了。远超能理解的程度。
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;@Overridepublic 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;// 开始计算Fnext.g = cur.g + 5; // 统一不开根号,这样可以提高精度,马走日,1 * 1 + 2 * 2 = 5next.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,因为我们给起点加了1System.out.println(moves[b1][b2] - 1);}scanner.close();}
}