题目地址:
https://leetcode.com/problems/checking-existence-of-edge-length-limited-paths-ii/
给定一个 n n n个顶点的无向带权图,要求在线回答若干询问,每次询问是个三元组 ( p , q , x ) (p,q,x) (p,q,x),是问是否存在 p p p到 q q q的每条边都小于 x x x的路径。
先以Kruskal算法求最小生成森林,显然对于任何 p p p和 q q q,它们之间所有路径中最大边最小的那条路径的最大边一定是最小生成树的某条边(如果存在路径的话)。建树完成之后,对每个连通块,以任意顶点为根,将该连通块做成一棵有根树。对于每次询问,我们只需求 p p p和 q q q所有到它们的最近公共祖先所经过的边的最大边权就行了。可以考虑用倍增思想(以下的内容可以参考https://blog.csdn.net/qq_46105170/article/details/116217633),开两个数组 f f f和 g g g,其中 f [ i ] [ k ] f[i][k] f[i][k]指的是从 i i i节点向上跳 2 k 2^k 2k步能过走到的顶点是谁(如果跳出界了则规定值为 − 1 -1 −1), g [ i ] [ k ] g[i][k] g[i][k]指的是从 i i i节点向上跳 2 k 2^k 2k的过程中经过的边的最大权值(在不会跳出界的情况下),然后从每个树根做BFS,初始化 f [ . ] [ 0 ] f[.][0] f[.][0]和 g [ . ] [ 0 ] g[.][0] g[.][0]。由于在询问的时候需要知道最近公共祖先,所以还需要一个数组 d d d记录每个顶点的深度,在BFS的时候可以同时求出。接下来,用倍增的思想计算 f f f和 g g g: f [ i ] [ k ] = f [ f [ i ] [ k − 1 ] ] [ k − 1 ] g [ i ] [ k ] = max { g [ i ] [ k − 1 ] , g [ f [ i ] [ k − 1 ] ] [ k − 1 ] } f[i][k]=f[f[i][k-1]][k-1]\\g[i][k]=\max\{g[i][k-1],g[f[i][k-1]][k-1]\} f[i][k]=f[f[i][k−1]][k−1]g[i][k]=max{g[i][k−1],g[f[i][k−1]][k−1]}至此,所有的预处理就完成了。
接下来询问的时候,如果 p p p与 q q q不连通则直接返回false。否则看一下两个顶点的深度,不妨设 p p p更深,则将 p p p向上跳若干步,使得 p p p与 q q q一样深,同时用经过的边权更新答案。此时如果 p = q p=q p=q,则答案已经求出,与 x x x比较即可;否则,将 p p p与 q q q继续向上跳,一路跳到它们的最近公共祖先的孩子的那层位置,一路更新答案,最后再用最近公共祖先与它们的连边更新答案,最后将答案与 x x x比较。代码如下:
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
public class DistanceLimitedPathsExist {
class UnionFind {
private int[] p;
public UnionFind(int size) {
p = new int[size];
for (int i = 0; i < size; i++) {
p[i] = i;
}
}
public int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
public void union(int x, int y) {
int px = find(x), py = find(y);
if (px != py) {
p[px] = py;
}
}
}
private UnionFind uf;
// 这里是链式前向星建图
private int[] h, e, ne, w;
private int idx;
private void add(int a, int b, int c) {
e[idx] = b;
ne[idx] = h[a];
w[idx] = c;
h[a] = idx++;
}
// 这里是倍增法所需的数组和变量
private int[][] f, g;
private int[] depth;
// n是顶点数,log是n对2的对数,log + 1也是f的第二维应该开的长度
private int n, log;
public DistanceLimitedPathsExist(int n, int[][] edgeList) {
this.n = n;
depth = new int[n];
h = new int[n];
Arrays.fill(h, -1);
// 无向图,边要开两倍
e = new int[n << 1];
ne = new int[n << 1];
w = new int[n << 1];
// 这一段是Kruskal算法建最小生成森林
Arrays.sort(edgeList, (e1, e2) -> Integer.compare(e1[2], e2[2]));
uf = new UnionFind(n);
for (int[] e : edgeList) {
int a = e[0], b = e[1], len = e[2];
if (uf.find(a) != uf.find(b)) {
uf.union(a, b);
add(a, b, len);
add(b, a, len);
}
}
log = (int) (Math.log(n) / Math.log(2));
f = new int[n][log + 1];
g = new int[n][log + 1];
for (int[] row : f) {
Arrays.fill(row, -1);
}
boolean[] vis = new boolean[n];
for (int i = 0; i < n; i++) {
if (!vis[i]) {
bfs(i, vis);
}
}
init();
}
// 递推一遍f和g数组
private void init() {
for (int i = 1; i < log + 1; i++) {
for (int j = 0; j < n; j++) {
if (f[j][i - 1] != -1) {
f[j][i] = f[f[j][i - 1]][i - 1];
g[j][i] = Math.max(g[j][i - 1], g[f[j][i - 1]][i - 1]);
}
}
}
}
// BFS一遍x所在连通块,并初始化f和g数组,并求出depth数组
private void bfs(int x, boolean[] vis) {
Queue<Integer> q = new ArrayDeque<>();
q.offer(x);
vis[x] = true;
while (!q.isEmpty()) {
int u = q.poll();
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (vis[v]) {
continue;
}
vis[v] = true;
f[v][0] = u;
g[v][0] = w[i];
q.offer(v);
depth[v] = depth[u] + 1;
}
}
}
public boolean query(int p, int q, int limit) {
if (uf.find(p) != uf.find(q)) {
return false;
}
if (depth[p] < depth[q]) {
int tmp = p;
p = q;
q = tmp;
}
// 先走到同一深度
int diff = depth[p] - depth[q];
int pow = 0, max = 0;
while (diff > 0) {
if ((diff & 1) == 1) {
max = Math.max(max, g[p][pow]);
p = f[p][pow];
}
pow++;
diff >>= 1;
}
// 已经走到同一点了,那深度更浅的那个点就是最近公共祖先,max就是经过的边的最大值
if (p == q) {
return max < limit;
}
// 否则跳到最近公共祖先下面一层,沿途更新答案
for (int i = log; i >= 0; i--) {
if (f[p][i] != f[q][i]) {
max = Math.max(max, g[p][i]);
max = Math.max(max, g[q][i]);
p = f[p][i];
q = f[q][i];
}
}
// 最后别忘了用最后一步更新答案
max = Math.max(max, g[p][0]);
max = Math.max(max, g[q][0]);
return max < limit;
}
}
初始化时间复杂度 O ( m log m + n log n ) O(m\log m+n\log n) O(mlogm+nlogn),每次询问时间 O ( log n ) O(\log n) O(logn),空间 O ( m + n + n log n ) O(m+n+n\log n) O(m+n+nlogn)。