1. 程式人生 > 實用技巧 >最小瓶頸路(Network) 加強版(Kruskal重構樹+尤拉序求lca)

最小瓶頸路(Network) 加強版(Kruskal重構樹+尤拉序求lca)

題目描述

給定一個 \(n\) 個點 \(m\) 條邊的無向連通圖,編號為 \(1\)\(n\) ,沒有自環,可能有重邊,每一條邊有一個正權值 \(w\)
給出 \(q\) 個詢問,每次給出兩個不同的點 \(u\)\(v\) ,求一條從 \(u\)\(v\) 的路徑上邊權的最大值最小是多少。

對於所有資料,\(n \le 70000\)\(m \le 100000\)\(q \le 10000000\)\(1 \le w_i \le 10^9\)

Analysis

這顯然是可以用最小生成樹+樹上倍增實現的,但是我們看到資料範圍就感到事情不妙,這個詢問必須\(O(1)\)求。這就要用到我們的Kruskal重構樹了。

什麼是Kruskal重構樹

Kruskal重構樹是在進行Kruskal演算法求最小生成樹時將邊權變為點權並將其連起來所構成的樹形結構。

如何構建

在執行Kruskal演算法時如果一條邊被選入了最小生成樹的集合,我們新建一個點,並將其點權賦值為該邊邊權。將新建的點連向該邊兩端點所在的連通塊的根節點,並將其合併,該新點作為新的根節點。

原來存在的點的點權即為\(0\)

程式碼實現:

for (register int i = 1; i <= (n << 1); ++i) fa[i] = i;
int limit = (n << 1) - 1;
for (int i = 1; i <= m; i++) {
	int x = ed[i].a;
	int y = ed[i].b;
	int fx = find(x);
	int fy = find(y);
	if (fx != fy) {
		cnt++;//新建一個點
		val[cnt] = ed[i].c;//點權賦值邊權
		add(cnt, fx); add(cnt, fy);//建邊
		fa[fx] = cnt; fa[fy] = cnt//將新點作為新的根節點
		if (cnt >= limit) break;//建好了直接break
	}
}

Kruskal重構樹的性質

1,共有\(2n-1\)個節點。

原來的n個點,最小生成樹上每條邊一個節點,共n-1個記節點。

2,這是一個二叉樹

顯然。

3,這是一個大根堆

因為後來加的節點的權值一定並前面的大,所以這也顯然。

4,原樹兩點之間的邊權最大值是重構樹上兩點Lca的權值

首先有個引理:這條邊一定在最小生成樹路徑上。

而我們發現Kruskal重構樹其實只是將邊換成了點,所以這個最長邊其實就是在重構樹上路徑上的最大點權。又因為這是大根堆所以一定是lca。

有了這些性質我們發現如果一個問題給了你一張圖,要你詢問兩點間瓶頸路的問題多半都可以用kruskal重構樹做。

現在來解決上面的題,其實就是個模板題,求兩點lca就行。可是倍增和樹剖都要\(O(\log{n})\)

的複雜度怎麼辦?其實還有一種\(O(1)\)求lca的方法。

我們先求出一棵樹的尤拉序,深度和每個節點在尤拉序中最早出現的位置。

尤拉序就是你在遍歷到每個節點在第一遍遍歷和遞迴遍歷完每個兒子後都加進去所形成的序列。這樣一共會有\(2n-1\)個,有趣的是證明和重構樹的性質1完全一樣。

這樣我們發現對於兩個節點其lca必然在其第一次出現的位置中間,且lca是深度最小的點,那我們可以用rmq來做,st單次查詢的時間複雜度為\(O(1)\)

完整程式碼:

/*
喂糖:C6H12O6 
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 140010;
const int M = 100010;
const int mod = 1e9 + 7;
template <typename T> void inline read(T &x) {
	T f = 1;
	char ch = getchar();
	for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1;
	for (x = 0; '0' <= ch && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
	x *= f;
}
struct node{
	int pre, to;
}edge[N];
int head[N], tot;
struct EDGE{
	int a, b, c;
	friend bool operator < (EDGE x, EDGE y) {
		return x.c < y.c;
	}
}ed[M];
int n, m, q, cnt;
ll A, B, C, P;
int fa[N], val[N];
int dfn[N << 1], eul;
int dep[N], first[N], st[N << 1][21];
int lg2[N << 1], ans;
int find(int x) {
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void add(int u, int v) {
	edge[++tot] = node{head[u], v};
	head[u] = tot;
}
void dfs(int x) {
	dfn[++eul] = x;
	first[x] = eul;
	for (int i = head[x]; i; i = edge[i].pre) {
		int y = edge[i].to;
		dep[y] = dep[x] + 1;
		dfs(y);
		dfn[++eul] = x;
	}
}
inline void st_init() {
	lg2[1] = 0;
	for (register int i = 2; i <= eul; i++) {
		lg2[i] = lg2[i >> 1] + 1;
	}
	for (register int i = 1; i <= eul; i++) {
		st[i][0] = dfn[i];
	}
	for (register int j = 1; j <= lg2[eul]; j++) {
		for (register int i = 1; i + (1 << j) - 1 <= eul; i++) {
			if (dep[st[i][j - 1]] < dep[st[i + (1 << (j - 1))][j - 1]]) {
				st[i][j] = st[i][j - 1];
			} else {
				st[i][j] = st[i + (1 << (j - 1))][j - 1];
			}
		}
	}
}
inline int lca(int u, int v) {
	if (first[u] > first[v]) swap(u, v);
	u = first[u]; v = first[v];
	int d = v - u + 1;
	int k = lg2[d];
	if (dep[st[u][k]] < dep[st[v - (1 << k) + 1][k]]) return st[u][k];
	return st[v - (1 << k) + 1][k];
}
inline ll rnd() {
	return A = (A * B % P + C) % P; 
}
int main() {
	read(n); read(m);
	cnt = n;
	for (register int i = 1; i <= m; ++i) {
		read(ed[i].a); read(ed[i].b); read(ed[i].c);
	}
	sort(ed + 1, ed + 1 + m);
	for (register int i = 1; i <= (n << 1); ++i) fa[i] = i;
	int limit = (n << 1) - 1;
	for (int i = 1; i <= m; i++) {
		int x = ed[i].a;
		int y = ed[i].b;
		int fx = find(x);
		int fy = find(y);
		if (fx != fy) {
			cnt++;
			val[cnt] = ed[i].c;
			add(cnt, fx);
			add(cnt, fy);
			fa[fx] = cnt;
			fa[fy] = cnt;
			if (cnt >= limit) break;
		}
	}
	dfs(cnt);
	st_init();
	read(q);
	read(A); read(B); read(C); read(P);
	while (q--) {
		int u = rnd() % n + 1;
		int v = rnd() % n + 1;
		ans = ans + val[lca(u, v)];
		while (ans >= mod) ans -= mod;
	}
	printf("%d", ans);
	return 0;
}