From 86de54de6a15a104d50b325b87010c6977c5232c Mon Sep 17 00:00:00 2001 From: Departuers <2644631299@qq.com> Date: Sat, 19 Sep 2020 15:31:20 +0800 Subject: [PATCH] c --- .../\346\216\222\344\271\246.java" | 7 +- .../BitSet\345\272\224\347\224\250.java" | 25 +++ ...\346\200\247\347\273\237\350\256\241.java" | 172 +++++++++++++++ .../topoSort/\345\245\226\351\207\221.java" | 120 ++++++++++- ...\347\253\231\345\210\206\347\272\247.java" | 10 + ...\345\205\265\347\221\236\346\201\251.java" | 200 +++++++++++++++++- ...\350\267\257\350\256\241\346\225\260.java" | 160 ++++++++++++++ .../\350\247\202\345\205\211.java" | 69 ++++++ ...\270\216\350\210\252\350\267\257spfa.java" | 109 ++++++++++ ...56\345\210\206\347\272\246\346\235\237.md" | 7 +- ...\346\263\274\346\260\264\350\212\202.java" | 2 - 11 files changed, 869 insertions(+), 12 deletions(-) create mode 100644 "ACWing/src/graph/topoSort/BitSet\345\272\224\347\224\250.java" create mode 100644 "ACWing/src/graph/topoSort/\345\217\257\350\276\276\346\200\247\347\273\237\350\256\241.java" create mode 100644 "ACWing/src/graph/topoSort/\350\275\246\347\253\231\345\210\206\347\272\247.java" create mode 100644 "ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\234\200\347\237\255\350\267\257\350\256\241\346\225\260.java" create mode 100644 "ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\350\247\202\345\205\211.java" create mode 100644 "ACWing/src/graph/\345\244\215\345\220\210\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257/\351\201\223\350\267\257\344\270\216\350\210\252\350\267\257spfa.java" diff --git "a/ACWing/src/DFS/\350\277\255\344\273\243\345\212\240\346\267\261/\346\216\222\344\271\246.java" "b/ACWing/src/DFS/\350\277\255\344\273\243\345\212\240\346\267\261/\346\216\222\344\271\246.java" index 562dc26..06df36e 100644 --- "a/ACWing/src/DFS/\350\277\255\344\273\243\345\212\240\346\267\261/\346\216\222\344\271\246.java" +++ "b/ACWing/src/DFS/\350\277\255\344\273\243\345\212\240\346\267\261/\346\216\222\344\271\246.java" @@ -39,7 +39,11 @@ import java.util.Scanner; * 选取的长度可能是1到n - 1。选取长度为1时,选第一个元素有n - 1种移动方法, * 选第二个元素有n - 2个移动方法,...,长度为1的情况就一共有n - 1 + n - 2 + ... + 1中方法去移动; * 长度为2时一共n - 2 + ... + 1种方法,长度为n - 1时一共1种移动方法, - * 总的移动方法有S = n - 1 + 2 * (n-2) + 3 * (n - 3) + ... + (n - 1) * 1 = (n + 2n + 3n + ... (n-1)n) - (1 + 4 + 9 + (n - 1)^2) = (n-1)n*n / 2 - n(n-1)(2n-1) / 6 = n(n-1)(n+1) / 6,当n = 15时,14 * 15 * 16 / 6 = 960。要想搜四层状态,就要搜索接近1000^4这么多种状态,显然无法承受,所以可以双向DFS将状态数降低到1000^2级别。使用IDA*则能够更快的解决问题。 + * 总的移动方法有S = n-1+2 * (n-2) + 3 * (n - 3) + ... + (n - 1) * 1 = + * (n + 2n + 3n + ... (n-1)n) - (1 + 4 + 9 + (n - 1)^2) = (n-1)n*n / 2 - n(n-1)(2n-1) / 6 = n(n-1)(n+1) / 6, + * 当n = 15时,14 * 15 * 16 / 6 = 960。要想搜四层状态,就要搜索接近1000^4这么多种状态, + * 显然无法承受,所以可以双向DFS将状态数降低到1000^2级别。使用IDA*则能够更快的解决问题。 + * * 我们知道,估价函数的值是不会超过当前状态离目标状态的真实距离的,所以一旦已搜索的深度u加上估价函数的值超过了设定的深度, * 就不必继续搜索了。如果每次只能移动一个元素,我们可以用每个元素离最终位置的距离作为估价函数,但是现在是批量移动元素。 * 比如1 2 3 4 5,将2 3移动到4的后面得到1 4 2 3 5,可以发现1的后继、4的后继、3的后继都改变了,而其它元素的后继未变, @@ -79,6 +83,7 @@ public class 排书 { for (int l = 0; l + len <= n; l++) { int r = l + len - 1; for (int k = r + 1; k < n; k++) { + } } } diff --git "a/ACWing/src/graph/topoSort/BitSet\345\272\224\347\224\250.java" "b/ACWing/src/graph/topoSort/BitSet\345\272\224\347\224\250.java" new file mode 100644 index 0000000..f89bdda --- /dev/null +++ "b/ACWing/src/graph/topoSort/BitSet\345\272\224\347\224\250.java" @@ -0,0 +1,25 @@ +package graph.topoSort; + +import java.util.BitSet; +import java.util.Scanner; + +/** + * 问题重述:一个最多包含n个正整数的文件,每个数都小于n,其中n=107,并且没有重复。 + * 最多有1MB内存可用。要求用最快方式将它们排序并按升序输出。 + */ +public class BitSet应用 { + public static void main(String[] args) { + + Scanner sc = new Scanner(System.in); + + BitSet bitSet = new BitSet(); + int n = sc.nextInt(); + for (int i = 0; i < n; i++) { + bitSet.set(sc.nextInt(),true); + } + for (int i = 0; i < bitSet.size(); i++) { + if (bitSet.get(i)) + System.out.println(i + " "); + } + } +} diff --git "a/ACWing/src/graph/topoSort/\345\217\257\350\276\276\346\200\247\347\273\237\350\256\241.java" "b/ACWing/src/graph/topoSort/\345\217\257\350\276\276\346\200\247\347\273\237\350\256\241.java" new file mode 100644 index 0000000..178ecbb --- /dev/null +++ "b/ACWing/src/graph/topoSort/\345\217\257\350\276\276\346\200\247\347\273\237\350\256\241.java" @@ -0,0 +1,172 @@ +package graph.topoSort; + +import java.io.*; +import java.util.BitSet; +import java.util.StringTokenizer; + +import static java.lang.System.in; + +/** + * https://blog.csdn.net/qq_41280600/article/details/102544874 + * 给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量。 + * 输入格式 + * 第一行两个整数N,M,接下来M行每行两个整数x,y,表示从x到y的一条有向边。 + * 输出格式 + * 输出共N行,表示每个点能够到达的点的数量。 + * 数据范围 + * 1≤N,M≤30000 + * 输入样例: + * 10 10 + * 3 8 + * 2 3 + * 2 5 + * 5 9 + * 5 9 + * 2 3 + * 3 9 + * 4 8 + * 2 10 + * 4 9 + * 1 + * 2 + * 3 + * 4 + * 5 + * 6 + * 7 + * 8 + * 9 + * 10 + * 11 + * 输出样例: + * 1 + * 6 + * 3 + * 3 + * 2 + * 1 + * 1 + * 1 + * 1 + * 1 + * 1 + * 2 + * 3 + * 4 + * 5 + * 6 + * 7 + * 8 + * 9 + * 10 + * 二、思路 + * 首先求出这幅图的拓扑序列,然后从最后往前进行统计每个点能够到达的点数。 + * 由于N=3e4 所以我们用二进制进行状态压缩。用一个N位二进制进行压缩, + * 每位1代表该点可以到达,0表示不可到达。 + *

+ * 由于是DAG,做dp + * 假设i能到j1,j2,j3...jn + * f[i]代表所有能从i点到的集合 + * f[i]={i}并上f[j1]并上f[j2]兵上f[j3]...∪f[jn] + * 所以可以按照topo排序的逆序遍历,分别求每个f[i]即可 + * 集合使用二进制表示,状态压缩10010 0代表不能到,1代表可以到,长度为n的二进制串 + */ +public class 可达性统计 { + public static void main(String[] args) throws IOException { + n = nextInt(); + m = nextInt(); + int a, b; + for (int i = 0; i < m; i++) { + a = nextInt(); + b = nextInt(); + add(a, b); + d[b]++; + } + toposort(); + for (int i = n - 1; i >= 0; i--) { + int j = q[i]; + f[j].set(j); + for (int k = h[j]; k != 0; k = ne[k]) { + f[j].or(f[e[k]]); + }//并上相邻的f[j] + } + for (int i = 1; i <= n; i++) { + bw.write(get(f[i]) + "\n"); + } + bw.flush(); + } + + static int get(BitSet b) { + int r = 0; + for (int i = 0; i < b.size(); i++) { + if (b.get(i)) r++; + } + return r; + } + + static int N = 30010, M = 30010, n, m, idx = 1; + static int[] h = new int[N]; + static int[] e = new int[M]; + static int[] ne = new int[M]; + static int[] d = new int[N]; + static int[] dist = new int[N]; + static BitSet[] f = new BitSet[N];//使用二进制记录集合 + static int[] q = new int[N]; + + static { + for (int i = 0; i < f.length; i++) { + f[i] = new BitSet(); + } + } + + static void toposort() { + int hh = 0, tt = -1; + for (int i = 1; i <= n; i++) { + if (d[i] == 0) { + q[++tt] = i; + } + } + while (hh <= tt) { + int t = q[hh++];//出队 + for (int i = h[t]; i != 0; i = ne[i]) { + int j = e[i]; + --d[j]; + if (d[j] == 0) { + q[++tt] = j; + } + } + } + } + + static void add(int a, int b) { + e[idx] = b; + ne[idx] = h[a]; + h[a] = idx++; + } + + + static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); + static BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + static StringTokenizer tokenizer = new StringTokenizer(""); + + static String nextLine() throws IOException {// 读取下一行字符串 + return reader.readLine(); + } + + static String next() throws IOException {// 读取下一个字符串 + while (!tokenizer.hasMoreTokens()) { + //如果没有字符了,就是下一个,使用空格拆分, + tokenizer = new StringTokenizer(reader.readLine()); + } + return tokenizer.nextToken(); + } + + static int nextInt() throws IOException {// 读取下一个int型数值 + return Integer.parseInt(next()); + } + + static double nextDouble() throws IOException {// 读取下一个double型数值 + return Double.parseDouble(next()); + } + +} diff --git "a/ACWing/src/graph/topoSort/\345\245\226\351\207\221.java" "b/ACWing/src/graph/topoSort/\345\245\226\351\207\221.java" index 992e7ca..786fb65 100644 --- "a/ACWing/src/graph/topoSort/\345\245\226\351\207\221.java" +++ "b/ACWing/src/graph/topoSort/\345\245\226\351\207\221.java" @@ -1,5 +1,12 @@ package graph.topoSort; +import java.io.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.StringTokenizer; + +import static java.lang.System.in; + /** * https://www.hzxueyan.com/archives/104/ * 由于无敌的凡凡在 2005 年世界英俊帅气男总决选中胜出,Yali Company 总经理 Mr.Z 心情好,决定给每位员工发奖金。 @@ -16,15 +23,124 @@ package graph.topoSort; * 否则输出一个数表示最少总奖金。 * 数据范围 * 1≤n≤10000 - * 1≤m≤20000 + * 1≤m≤20000 * 输入样例 * 2 1 * 1 2 * 输出样例 * 201 + * input + * 3 2 + * 1 2 + * 1 3 + * out: + * 301 + * 差分约束问题, a>=b+1 + * “我认为员工 a 的奖金应该比 b 高!”,相当于存在一条有向边,边权为1,从 b 连接到 a, + * 三角不等式 + * 求最长路,最小值 + * 所以用拓扑排序 + *

+ * 表示 a 比 b 的奖金高,按照此方法建立一个图出来 + * 为了方便理解,记umap[i] = 100表示员工 i 的奖金为 100100 + * 每次将入度为0 的点加入进队列时,需要将这些点的奖金设置为 100100 + * 然后取出对头点 nownow 所连接的点 v,它的奖金就应该是umap[v] = umap[now] + 1, + * 因为入度为 0 的点所指向的点 v,表示员工 now 比员工 v 的奖金低, + * 由于奖金是整数,所以这里加 1。 + *

+ * 起始奖金为100,虚拟源点,连向其他点,边权为100 + * 初值设为100,dist[i]=100 */ public class 奖金 { - public static void main(String[] args) { + public static void main(String[] args) throws IOException { + n = nextInt(); + m = nextInt(); + int a, b; + while (m-- != 0) { + a = nextInt(); + b = nextInt(); + add(b, a); + d[a]++;//入度 + } + if (toposort()) { + for (int i = 1; i <= n; i++) { + dist[i] = 100; + }//所有初始值为100 + + //拓扑图求最长路 + for (int i = 0; i < n; i++) { + int j = res.get(i); + for (int k = h[j]; k != 0; k = ne[k]) { + int tt = e[k]; + dist[tt] = Math.max(dist[tt], dist[j] + 1); + } + } + int res = 0; + for (int i = 1; i <= n; i++) { + res += dist[i]; + } + System.out.println(res); + } else System.out.println("Poor Xed"); + } + + static ArrayList res = new ArrayList(); + + private static boolean toposort() { + for (int i = 1; i <= n; i++) { + if (d[i] == 0) { + q.add(i); + res.add(i); + } + } + while (!q.isEmpty()) { + int t = q.poll(); + for (int i = h[t]; i != 0; i = ne[i]) { + int j = e[i]; + --d[j]; + if (d[j] == 0) { + res.add(j); + q.add(j); + } + } + } + return res.size() == n; + } + + static void add(int a, int b) { + e[idx] = b; + ne[idx] = h[a]; + h[a] = idx++; + } + + static int N = 10010, M = 20010, n, m, idx = 1; + static int[] h = new int[N]; + static int[] e = new int[M]; + static int[] ne = new int[M]; + static int[] d = new int[N]; + static int[] dist = new int[N]; + static ArrayDeque q = new ArrayDeque(); + static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); + static BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + static StringTokenizer tokenizer = new StringTokenizer(""); + static String nextLine() throws IOException {// 读取下一行字符串 + return reader.readLine(); } + + static String next() throws IOException {// 读取下一个字符串 + while (!tokenizer.hasMoreTokens()) { + //如果没有字符了,就是下一个,使用空格拆分, + tokenizer = new StringTokenizer(reader.readLine()); + } + return tokenizer.nextToken(); + } + + static int nextInt() throws IOException {// 读取下一个int型数值 + return Integer.parseInt(next()); + } + + static double nextDouble() throws IOException {// 读取下一个double型数值 + return Double.parseDouble(next()); + } + } diff --git "a/ACWing/src/graph/topoSort/\350\275\246\347\253\231\345\210\206\347\272\247.java" "b/ACWing/src/graph/topoSort/\350\275\246\347\253\231\345\210\206\347\272\247.java" new file mode 100644 index 0000000..7218309 --- /dev/null +++ "b/ACWing/src/graph/topoSort/\350\275\246\347\253\231\345\210\206\347\272\247.java" @@ -0,0 +1,10 @@ +package graph.topoSort; + +/** + * + */ +public class 车站分级 { + public static void main(String[] args) { + + } +} diff --git "a/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\213\257\346\225\221\345\244\247\345\205\265\347\221\236\346\201\251.java" "b/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\213\257\346\225\221\345\244\247\345\205\265\347\221\236\346\201\251.java" index 6a3a0cd..cabe55a 100644 --- "a/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\213\257\346\225\221\345\244\247\345\205\265\347\221\236\346\201\251.java" +++ "b/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\213\257\346\225\221\345\244\247\345\205\265\347\221\236\346\201\251.java" @@ -1,5 +1,10 @@ package graph.单源最短路拓展; +import java.io.*; +import java.util.*; + +import static java.lang.System.in; + /** * 拿锁bfs * https://blog.csdn.net/qq_30277239/article/details/106819891 @@ -49,15 +54,198 @@ package graph.单源最短路拓展; * 14 * 样例解释: * 迷宫如下所示: - * 本题y总的思路看到一半,发现与自己的想法有所出入,再看下y总的代码,大致理解了其思路,又觉得本题完全可以不用双端队列BFS求解,于是用普通的BFS求解,果然AC了,而且效率也不低。 - * 试想下如果本题没有钥匙和门的条件,只要求从左上角走到右下角的最小步数,就是简单的迷宫问题了,可以使用BFS解决。加上钥匙和门的的条件,便是类似于八数码问题了。实际上BFS解决的最短路问题都可以看作求从初始状态到结束状态需要的最小转移次数,普通迷宫问题的状态就是当前所在的坐标,八数码问题的状态就是当前棋盘的局面。本题在迷宫问题上加上了钥匙和门的条件,显然,处在同一个坐标下,持有钥匙和不持有钥匙就不是同一个状态了,为了能够清楚的表示每个状态,除了当前坐标外还需要加上当前获得的钥匙信息,即f[x][y][st]表示当前处在(x,y)位置下持有钥匙状态为st,将二维坐标压缩成一维就得到f[z][st]这样的状态表示了,或者说,z是格子的编号,从上到下,从左而右的编号依次为1到n*m,st为0110时,表示持有第1,2类钥匙,这里注意我在表示状态时抛弃了最右边的一位,因为钥匙编号从1开始,我想确定是否持有第i类钥匙时,只需要判断st >> i & 1是不是等于1即可。 - * 知道了状态表示,现在题目就转化为了从状态f[1][0]转化为f[n*m][...]状态的最小步数了,我们不关心到达终点是什么状态,只要到达了终点就成功了。现在进行第二步状态转移,两个相邻格子间有墙,就不能转移;有门,持有该类门钥匙就能转移,没有钥匙就不能转移;没有障碍,正常转移。下面讨论转移到有钥匙的格子的情况,这点我与y总处理方式的不同决定了最终解法的不同,y总是先不管有没有钥匙,先转移到这个格子再说,转移到有钥匙的格子的时候步数加一,然后拿起钥匙不移动位置进入另一种状态步数不变。我最初的想法就是,为什么要把这两个过程分开,我们走到有钥匙的格子上,并不用考虑要不要拿钥匙,拿钥匙又不会增加成本,只管拿就行。因此,转移到某个格子时,直接计算下这个格子的状态,格子上有钥匙就在之前状态基础上加上这个钥匙,没有钥匙就继承之前的钥匙状态,这样一来,问题中就不存在边权为0的边了,只要状态转移了,步长都是加一,普通的BFS就可以解决了。 - * 按照DP的一般流程,状态表示和状态转移分析完就可以解决问题了。回想下最开始的摘花生问题,也是从左上角走到右下角,摘尽可能多的花生,是不是这题也能够类似去处理呢?观察摘花生问题条件可以发现,题目限制了只能向下或者向右走,类似的问题也都限制了总步数,这是在为我们用迭代的形式进行状态转移提供方便,而本题可以向上下左右进行状态转移,迭代的形式不好实现,用BFS进行状态转移却很方便。既然本题是类似于八数码问题,自然也可以使用A*算法解决,这里就仅用普通的BFS去求解了。 - * 观察输入的格式,首先读入两个格子坐标以及之间边的类型,读入x1,y1,x2,y2,Gi后,两个格子的坐标可以压缩为z1,z2两个编号,直接g[z1][z2]=g[z2][z1]=Gi即可,因为是双向边,所以要存两次。接着读入哪些坐标放了钥匙,如果一个格子只放一把钥匙,直接key[z] = k即可,但是条件是可以放多把钥匙,就直接用key数组记录下该坐标下存放的所有钥匙的状态吧,读入时key[z] |= k就行了。BFS的过程不再赘述,放进队列里的应该是由格子编号z和持有钥匙状态st构成的二元组,初始状态是{1,key[1]},然后在队列非空时不断出队,取队头元素,尝试向四个方向转移,注意这里我是将格子编号从1开始,因此将其转化为坐标时需要先减一再加一。从0开始编号的话,x = z / m,y = z % m,m是列数;从1开始编号的话x = (z - 1) / m + 1,y = (z - 1) % m + 1,这里从1开始编号的转换特别容易出错。如果转移到棋盘外,或者遇见墙了就不向这个方向转移;如果从当前状态到下一个格子间有门,看下当前状态是否有该类门的钥匙,有就转移,不然不转移。转移到新格子后,新格子上有钥匙就尝试更新下状态,如果新格子的状态之前没有到达过就加入到队列里,到达终点就返回结果。 + * 本题y总的思路看到一半,发现与自己的想法有所出入,再看下y总的代码,大致理解了其思路, + * 又觉得本题完全可以不用双端队列BFS求解,于是用普通的BFS求解,果然AC了,而且效率也不低。 + * 试想下如果本题没有钥匙和门的条件,只要求从左上角走到右下角的最小步数,就是简单的迷宫问题了, + * 可以使用BFS解决。加上钥匙和门的的条件,便是类似于八数码问题了。 + * 实际上BFS解决的最短路问题都可以看作求从初始状态到结束状态需要的最小转移次数, + * 普通迷宫问题的状态就是当前所在的坐标,八数码问题的状态就是当前棋盘的局面。 + * 本题在迷宫问题上加上了钥匙和门的条件,显然,处在同一个坐标下, + * 持有钥匙和不持有钥匙就不是同一个状态了,为了能够清楚的表示每个状态,除了当前坐标外还需要加上当前获得的钥匙信息, + * 即f[x][y][st]表示当前处在(x,y)位置下持有钥匙状态为st,将二维坐标压缩成一维就得到f[z][st]这样的状态表示了, + * 或者说,z是格子的编号,从上到下,从左而右的编号依次为1到n*m,st为0110时,表示持有第1,2类钥匙, + * 这里注意我在表示状态时抛弃了最右边的一位,因为钥匙编号从1开始,我想确定是否持有第i类钥匙时, + * 只需要判断st >> i & 1是不是等于1即可。 + * 知道了状态表示,现在题目就转化为了从状态f[1][0]转化为f[n*m][...]状态的最小步数了, + * 我们不关心到达终点是什么状态,只要到达了终点就成功了。现在进行第二步状态转移,两个相邻格子间有墙, + * 就不能转移;有门,持有该类门钥匙就能转移,没有钥匙就不能转移;没有障碍,正常转移。 + * 下面讨论转移到有钥匙的格子的情况,这点我与y总处理方式的不同决定了最终解法的不同, + * y总是先不管有没有钥匙,先转移到这个格子再说,转移到有钥匙的格子的时候步数加一, + * 然后拿起钥匙不移动位置进入另一种状态步数不变。我最初的想法就是,为什么要把这两个过程分开, + * 我们走到有钥匙的格子上,并不用考虑要不要拿钥匙,拿钥匙又不会增加成本,只管拿就行。因此, + * 转移到某个格子时,直接计算下这个格子的状态,格子上有钥匙就在之前状态基础上加上这个钥匙, + * 没有钥匙就继承之前的钥匙状态,这样一来,问题中就不存在边权为0的边了,只要状态转移了,步长都是加一,普通的BFS就可以解决了。 + * 按照DP的一般流程,状态表示和状态转移分析完就可以解决问题了。回想下最开始的摘花生问题, + * 也是从左上角走到右下角,摘尽可能多的花生,是不是这题也能够类似去处理呢?观察摘花生问题条件可以发现, + * 题目限制了只能向下或者向右走,类似的问题也都限制了总步数,这是在为我们用迭代的形式进行状态转移提供方便, + * 而本题可以向上下左右进行状态转移,迭代的形式不好实现,用BFS进行状态转移却很方便。既然本题是类似于八数码问题, + * 自然也可以使用A*算法解决,这里就仅用普通的BFS去求解了。 + * 观察输入的格式,首先读入两个格子坐标以及之间边的类型,读入x1,y1,x2,y2,Gi后, + * 两个格子的坐标可以压缩为z1,z2两个编号,直接g[z1][z2]=g[z2][z1]=Gi即可,因为是双向边, + * 所以要存两次。接着读入哪些坐标放了钥匙,如果一个格子只放一把钥匙,直接key[z] = k即可, + * 但是条件是可以放多把钥匙,就直接用key数组记录下该坐标下存放的所有钥匙的状态吧,读入时key[z] |= k就行了。 + * BFS的过程不再赘述,放进队列里的应该是由格子编号z和持有钥匙状态st构成的二元组,初始状态是{1,key[1]}, + * 然后在队列非空时不断出队,取队头元素,尝试向四个方向转移,注意这里我是将格子编号从1开始, + * 因此将其转化为坐标时需要先减一再加一。从0开始编号的话,x = z / m,y = z % m,m是列数; + * 从1开始编号的话x = (z - 1) / m + 1,y = (z - 1) % m + 1,这里从1开始编号的转换特别容易出错。 + * 如果转移到棋盘外,或者遇见墙了就不向这个方向转移;如果从当前状态到下一个格子间有门, + * 看下当前状态是否有该类门的钥匙,有就转移,不然不转移。转移到新格子后,新格子上有钥匙就尝试更新下状态, + * 如果新格子的状态之前没有到达过就加入到队列里,到达终点就返回结果。 * 本题看起来复杂,实际上不过是动态规划、BFS和状态压缩三者的结合,还是比较简单的,总的代码如下: */ public class 拯救大兵瑞恩 { - public static void main(String[] args) { + public static void main(String[] args) throws IOException { + n = nextInt(); + m = nextInt(); + p = nextInt(); + k = nextInt(); + for (int i = 1, t = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + g[i][j] = t++; + } + } + int x1, x2, y1, y2, c; + + while (k-- != 0) { + x1 = nextInt(); + y1 = nextInt(); + x2 = nextInt(); + y2 = nextInt(); + c = nextInt(); + int a = g[x1][y1], b = g[x2][y2]; + edges.add(new node(a, b)); + edges.add(new node(b, a)); + if (c != 0) { + add(a, b, c); + add(b, a, c); + } + } + int x, y, id; + int s = nextInt(); + while (s-- != 0) { + x = nextInt(); + y = nextInt(); + id = nextInt(); + key[g[x][y]] |= 1 << id - 1;//偏移量从0开始 + } + build(); + System.out.println(bfs()); + } + + static void build() { + int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= m; j++) { + for (int u = 0; u < 4; u++) { + int x = i + dir[u][0], y = j + dir[u][1]; + if (x <= 0 || x > n || y <= 0 || y >= m) continue; + int a = g[i][j], b = g[x][y]; + if (!edges.contains(new node(a, b))) { + add(a, b, 0); + } + } + } + } + } + + static int inf = 0x3f3f3f3f; + + static int bfs() { + for (int i = 0; i < dist.length; i++) { + Arrays.fill(dist[i], inf); + } + dist[1][0] = 0;//初始化在第一个位置,没钥匙 + ArrayDeque q = new ArrayDeque(); + q.add(new node(1, 0)); + while (!q.isEmpty()) {//st和Dijkstra的st概念一样 + node t = q.poll(); + if (st[t.x][t.y]) continue; + st[t.x][t.y] = true; + if (key[t.x] != 0) { + int state = t.y | key[t.x]; + if (dist[t.x][state] > dist[t.x][t.y]) { + dist[t.x][state] = dist[t.x][t.y]; + q.addFirst(new node(t.x, state)); + } + } + for (int i = h[t.x]; i != 0; i = ne[i]) { + int j = e[i]; + if (w[i] != 0 && (t.y >> w[i] - 1 & 1) == 0) continue; + if (dist[j][t.y] > dist[t.x][t.y] + 1) { + dist[j][t.y] = dist[t.x][t.y] + 1; + q.add(new node(j, t.y)); + } + } + } + return -1; + } + + private static void add(int a, int b, int c) { + e[idx] = b; + w[idx] = c; + ne[idx] = h[a]; + h[a] = idx++; + } + + static class node { + int x, y; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + node node = (node) o; + + if (x != node.x) return false; + return y == node.y; + } + + @Override + public int hashCode() { + int result = x; + result = 31 * result + y; + return result; + } + + public node(int x, int y) { + this.x = x; + this.y = y; + } + } + + static int N = 11, M = N * N, E = 400, k, P = 1 << 10, n, m, p, idx = 1;//最多有10种钥匙 + //400条边,竖边有n*n-1 一共有(n*n-1)*2*2 + static int[] h = new int[M]; + static int[] e = new int[E]; + static int[] ne = new int[E]; + static int[] w = new int[E]; + static int[][] g = new int[N][N]; + static int[] key = new int[M]; + static int[][] dist = new int[M][P];//二维坐标映射一维 + static boolean[][] st = new boolean[M][P]; + static HashSet edges = new HashSet(); + static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); + static BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + static StringTokenizer tokenizer = new StringTokenizer(""); + + static String nextLine() throws IOException {// 读取下一行字符串 + return reader.readLine(); + } + + static String next() throws IOException {// 读取下一个字符串 + while (!tokenizer.hasMoreTokens()) { + //如果没有字符了,就是下一个,使用空格拆分, + tokenizer = new StringTokenizer(reader.readLine()); + } + return tokenizer.nextToken(); + } + + static int nextInt() throws IOException {// 读取下一个int型数值 + return Integer.parseInt(next()); + } + static double nextDouble() throws IOException {// 读取下一个double型数值 + return Double.parseDouble(next()); } } diff --git "a/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\234\200\347\237\255\350\267\257\350\256\241\346\225\260.java" "b/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\234\200\347\237\255\350\267\257\350\256\241\346\225\260.java" new file mode 100644 index 0000000..5b7459a --- /dev/null +++ "b/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\346\234\200\347\237\255\350\267\257\350\256\241\346\225\260.java" @@ -0,0 +1,160 @@ +package graph.单源最短路拓展; + +import java.io.*; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.StringTokenizer; + +import static java.lang.System.in; + +/** + * https://blog.csdn.net/qq_30277239/article/details/106864304 + * 给出一个 N 个顶点 M 条边的无向无权图,顶点编号为 1 到 N。 + * 问从顶点 1 开始,到其他每个点的最短路有几条。 + * 输入格式 + * 第一行包含 2 个正整数 N,M,为图的顶点数与边数。 + * 接下来 M 行,每行两个正整数 x,y,表示有一条顶点 x 连向顶点 y 的边,请注意可能有自环与重边。 + * 输出格式 + * 输出 N 行,每行一个非负整数,第 i 行输出从顶点 1 到顶点 i 有多少条不同的最短路,由于答案有可能会很大,你只需要输出对 100003 取模后的结果即可。 + * 如果无法到达顶点 i 则输出 0。 + * 数据范围 + * 1≤N≤10^5, + * 1≤M≤2×10^5 + * 输入样例: + * 5 7 + * 1 2 + * 1 3 + * 2 4 + * 3 4 + * 2 3 + * 4 5 + * 4 5 + * 输出样例: + * 1 + * 1 + * 1 + * 2 + * 4 + * 本题类似于背包问题的方案数,如果是求起点到终点所有的路径条数,直接遍历图的同时统计下方案数即可。 + * 现在要求的是最短路径的条数,因此应该在计算最短路径的过程中使用动态规划的思想去求解。 + * 本题中涉及的边都是无权边,或者说边权都是1,因此可以直接用bfs来求最短路径。 + * 回忆下基本的BFS过程:将起点加入队列,访问位置为true;当队列非空时不断取队头元素, + * 将队头元素相邻的访问位为false的点都加入到队列中来,同时更新这些点的最短路径。 + * 这意味着如果从a扩展到b点时,将b入队了,然后即使从c点扩展到b点, + * 并且这两种扩展的路径中b的最短路径都是相同的,但是由于b由a扩展了已经入队了, + * 再次由c扩展到时不会再次入队了,而统计方案数则要统计所有的最短路径情况。 + * 因此,设d[i]为目前起点到i点的最短路径,cnt[i]是目前起点到i点最短路径的条数, + * 如果从上一点u向i点扩展,d[i] < d[u] + 1时,需要更新最短路径长度,d[i] = d[u] + 1, + * 并且此刻到i的最短路径长度应该继承到u的最短路径条数,即cnt[i] = cnt[u];d[i] == d[u] + 1时, + * 不需要更新最短路径长度,但是需要将到u的最短路径条数加到到i的最短路径条数中,即cnt[i] += cnt[u]。 + * 最短路径长度和最短路径条数更新的条件已经明确了,最后要明确的就是何时将节点加入到队列中? + * 从u扩展到i,但是d[u] + 1 > d[i],此时自然不用将i入队;d[u] + 1 < d[i], + * 这时候更新了i点的最短路径长度,因此要将i入队;那么d[u] + 1 == d[i]时呢?此刻由于d[i]与d[u] + 1相等, + * 必然已经经由其他点扩展过加入队列了,而比d[i]更小的d[u]才刚刚出队,d[i]必然还在队列中, + * 因此不用再次将i入队了。这样一来,标志数组st也就没有必要设置了。 + * 对于题目中的重边和自环,重边的存在有可能将最短路径条数翻倍,自环的存在可以忽略, + * 一个点走一遍自环会使得到起点的距离加上1,必然不是最短距离。总的代码如下: + *

+ * dp思想 + * 1.先求出全局最小值, + * 2.分别求出每个子集中高等于全局最小值的元素个数 + *

+ * dist[i] 表示所有从1到i号点,所有路径长度最短的值是多少 + * cnt[i] 表示从1到i最短路径的条数 + * 根据最后一条到i的边的方向,分类 + * t->j dist[j]=dist[t]+w[i] + * 记录前驱, + * 如果有一个环权值和为0,那么最短路条数为正无穷 + * bfs 每个点只入队一次,只出队一次, + * Dijkstra 每个点,只作为最小值出队一次,每个点第一次出队序列满足拓扑序 + * 前面可以顺便统计count + * bellman-ford(spfa) 本身不具备拓扑序 + */ +public class 最短路计数 { + public static void main(String[] args) throws IOException { + n = nextInt(); + m = nextInt(); + int a, b; + while (m-- != 0) { + a = nextInt(); + b = nextInt(); + add(a, b); + add(b, a); + } + bfs(); + for (int i = 1; i <= n; i++) { + if (i == n) bw.write(cnt[i] + ""); + else bw.write(cnt[i] + "\n"); + } + bw.flush(); + } + + static int inf = Integer.MAX_VALUE / 2 - 199; + + static void bfs() { + ArrayDeque q = new ArrayDeque(); + Arrays.fill(dis, inf); + + dis[1] = 0; + cnt[1] = 1;//1号点的最短路径条数是1 + q.add(1); + while (!q.isEmpty()) { + int u = q.poll(); + for (int i = h[u]; i != 0; i = ne[i]) { + int j = e[i]; + + //核心部分,看做dp的转移 + if (dis[j] > dis[u] + 1) { + cnt[j] = cnt[u]; + dis[j] = dis[u] + 1; + + q.add(j); + } else if (dis[j] == dis[u] + 1) { + cnt[j] %= mod; + cnt[j] = (cnt[j] + cnt[u]) % mod; + } + + + } + + } + } + + static int N = 100005,M = 400005, mod = 100003, n, m, idx=1; + static int[] h = new int[N]; + static int[] e = new int[M]; + static int[] ne = new int[M]; + static int[] dis = new int[N]; + static int[] cnt = new int[N]; + + static void add(int a, int b) { + e[idx] = b; + ne[idx] = h[a]; + h[a] = idx++; + } + + static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); + static BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + static StringTokenizer tokenizer = new StringTokenizer(""); + + static String nextLine() throws IOException {// 读取下一行字符串 + return reader.readLine(); + } + + static String next() throws IOException {// 读取下一个字符串 + while (!tokenizer.hasMoreTokens()) { + //如果没有字符了,就是下一个,使用空格拆分, + tokenizer = new StringTokenizer(reader.readLine()); + } + return tokenizer.nextToken(); + } + + static int nextInt() throws IOException {// 读取下一个int型数值 + return Integer.parseInt(next()); + } + + static double nextDouble() throws IOException {// 读取下一个double型数值 + return Double.parseDouble(next()); + } + +} diff --git "a/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\350\247\202\345\205\211.java" "b/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\350\247\202\345\205\211.java" new file mode 100644 index 0000000..4e64b7a --- /dev/null +++ "b/ACWing/src/graph/\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257\346\213\223\345\261\225/\350\247\202\345\205\211.java" @@ -0,0 +1,69 @@ +package graph.单源最短路拓展; + +/** + * https://blog.csdn.net/qq_30277239/article/details/107073569 + * “您的个人假期”旅行社组织了一次比荷卢经济联盟的巴士之旅。 + * 比荷卢经济联盟有很多公交线路。 + * 每天公共汽车都会从一座城市开往另一座城市。 + * 沿途汽车可能会在一些城市(零或更多)停靠。 + * 旅行社计划旅途从 S 城市出发,到 F 城市结束。 + * 由于不同旅客的景点偏好不同,所以为了迎合更多旅客, + * 旅行社将为客户提供多种不同线路。 + * 游客可以选择的行进路线有所限制,要么满足所选路线总路程为 S 到 F 的最小路程, + * 要么满足所选路线总路程仅比最小路程多一个单位长度。 + * 如上图所示,如果S = 1,F = 5,则这里有两条最短路线1->2->5,1->3->5, + * 长度为6;有一条比最短路程多一个单位长度的路线1->3->4->5,长度为7。 + * 现在给定比荷卢经济联盟的公交路线图以及两个城市 S 和 F, + * 请你求出旅行社最多可以为旅客提供多少种不同的满足限制条件的线路。 + * 输入格式 + * 第一行包含整数 T,表示共有 T 组测试数据。 + * 每组数据第一行包含两个整数 N 和 M,分别表示总城市数量和道路数量。 + * 接下来 M 行,每行包含三个整数 A,B,L,表示有一条线路从城市 A 通往城市 B,长度为 L。 + * 需注意,线路是 单向的,存在从A到B的线路不代表一定存在从B到A的线路, + * 另外从城市A到城市B可能存在多个不同的线路。 + * 接下来一行,包含两个整数 S 和 F,数据保证 S 和 F 不同,并且S、F之间至少存在一条线路。 + * 输出格式 + * 每组数据输出一个结果,每个结果占一行。 + * 数据保证结果不超过10^9。 + * 数据范围 + * 2≤N≤1000, + * 1≤M≤10000, + * 1≤L≤1000, + * 1≤A,B,S,F≤N + * 输入样例: + * 2 + * 5 8 + * 1 2 3 + * 1 3 2 + * 1 4 5 + * 2 3 1 + * 2 5 3 + * 3 4 2 + * 3 5 4 + * 4 5 3 + * 1 5 + * 5 6 + * 2 3 1 + * 3 2 1 + * 3 1 10 + * 4 5 2 + * 5 2 7 + * 5 2 7 + * 4 1 + * 输出样例: + * 3 + * 2 + * 统计最短路径的条数 + * 统计次短路径的条数 + * :如果存在比最短路径长度恰好多1的路径,则再把这样的路径条数加上 + * d[i,0]表示从1到i的最短路径 + * d[i,1]表示从1到i的次短路径 + * cnt[i,1] 表示从1到i的次短路 + */ +public class 观光 { + public static void main(String[] args) { + + } + + static int N = 1010, n, m, M; +} diff --git "a/ACWing/src/graph/\345\244\215\345\220\210\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257/\351\201\223\350\267\257\344\270\216\350\210\252\350\267\257spfa.java" "b/ACWing/src/graph/\345\244\215\345\220\210\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257/\351\201\223\350\267\257\344\270\216\350\210\252\350\267\257spfa.java" new file mode 100644 index 0000000..e14bbde --- /dev/null +++ "b/ACWing/src/graph/\345\244\215\345\220\210\345\215\225\346\272\220\346\234\200\347\237\255\350\267\257/\351\201\223\350\267\257\344\270\216\350\210\252\350\267\257spfa.java" @@ -0,0 +1,109 @@ +package graph.复合单源最短路; + +import java.io.*; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.StringTokenizer; + +/** + * slf优化,但... + * 比cpp慢一半,hh + */ +public class 道路与航路spfa { + public static void main(String[] args) throws IOException { + T = nextInt(); + R = nextInt(); + P = nextInt(); + S = nextInt(); + int a, b, c; + for (int i = 0; i < R; i++) { + a = nextInt(); + b = nextInt(); + c = nextInt(); + add(a, b, c); + add(b, a, c); + } + for (int i = 0; i < P; i++) { + a = nextInt(); + b = nextInt(); + c = nextInt(); + add(a, b, c); + } + spfa(); + } + + static int n, m, N = 25010, M = 150010, idx = 1, T, R, P, S; + static int[] h = new int[N]; + static int[] w = new int[M]; + static int[] e = new int[M]; + static int[] ne = new int[M]; + static int[] dist = new int[N]; + static boolean[] st = new boolean[N]; + + static void add(int a, int b, int c) { + e[idx] = b; + w[idx] = c; + ne[idx] = h[a]; + h[a] = idx++; + } + + static int inf = 0x3f3f3f3f; + + static void spfa() throws IOException { + Arrays.fill(dist, inf); + ArrayDeque q = new ArrayDeque(); + q.add(S); + dist[S] = 0; + while (!q.isEmpty()) { + int p = q.poll(); + //SLF swap:每当队列改变时,如果队首距离大于队尾,则交换首尾。 + if (q.size() > 2 && dist[q.peekFirst()] > dist[q.peekLast()]) { + int a = q.pollLast(); + q.add(q.pollFirst()); + q.addFirst(a); + } + st[p] = false; + for (int i = h[p]; i != 0; i = ne[i]) { + int j = e[i]; + if (dist[j] > dist[p] + w[i]) { + dist[j] = dist[p] + w[i]; + if (!st[j]) { + st[j] = true; + if (!q.isEmpty() && dist[j] < dist[q.peekFirst()]) { + q.addFirst(j); + } else q.add(j); + } + } + } + } + for (int i = 1; i <= T; i++) { + if (dist[i] == inf) bw.write("NO PATH\n"); + else bw.write(dist[i] + "\n"); + } + bw.flush(); + } + + static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out)); + static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + static StringTokenizer tokenizer = new StringTokenizer(""); + + static String nextLine() throws IOException {// 读取下一行字符串 + return reader.readLine(); + } + + static String next() throws IOException {// 读取下一个字符串 + while (!tokenizer.hasMoreTokens()) { + //如果没有字符了,就是下一个,使用空格拆分, + tokenizer = new StringTokenizer(reader.readLine()); + } + return tokenizer.nextToken(); + } + + static int nextInt() throws IOException {// 读取下一个int型数值 + return Integer.parseInt(next()); + } + + static double nextDouble() throws IOException {// 读取下一个double型数值 + return Double.parseDouble(next()); + } +} diff --git "a/ACWing/src/graph/\345\267\256\345\210\206\347\272\246\346\235\237/\345\267\256\345\210\206\347\272\246\346\235\237.md" "b/ACWing/src/graph/\345\267\256\345\210\206\347\272\246\346\235\237/\345\267\256\345\210\206\347\272\246\346\235\237.md" index 0e00d60..c6b335a 100644 --- "a/ACWing/src/graph/\345\267\256\345\210\206\347\272\246\346\235\237/\345\267\256\345\210\206\347\272\246\346\235\237.md" +++ "b/ACWing/src/graph/\345\267\256\345\210\206\347\272\246\346\235\237/\345\267\256\345\210\206\347\272\246\346\235\237.md" @@ -1,9 +1,14 @@ #差分约束 ###功能 -```` +``` 1:求不等式的可行解, 源点满足,从源点出发,一定可以走到所有的边 2:求最大值或最小值 +``` +```` +边权无限制, spfa O(nm) +边权非负 tarjan O(n+m) +边权大于0 拓扑排序 O(n+m) ```` ###方法 ```` diff --git "a/ACWing/src/graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\213\223\345\261\225/\350\265\260\345\273\212\346\263\274\346\260\264\350\212\202.java" "b/ACWing/src/graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\213\223\345\261\225/\350\265\260\345\273\212\346\263\274\346\260\264\350\212\202.java" index fa5553b..96eec94 100644 --- "a/ACWing/src/graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\213\223\345\261\225/\350\265\260\345\273\212\346\263\274\346\260\264\350\212\202.java" +++ "b/ACWing/src/graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221\346\213\223\345\261\225/\350\265\260\345\273\212\346\263\274\346\260\264\350\212\202.java" @@ -1,7 +1,6 @@ package graph.最小生成树拓展; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Scanner; @@ -36,7 +35,6 @@ public class 走廊泼水节 { int a = find(c.x), b = find(c.y); if (a != b) { res += (size[a] * size[b] - 1) * (c.w + 1);//新边都取w+1 - System.out.println(Arrays.toString(size)); size[b] += size[a];//合并两个集合 par[a] = b;//a指向b } -- GitLab