最小生成树和次小生成树
最小生成树,就是图上边权和最小的生成树。
令 \(n\) 为图上节点数,\(m\) 为图的边数。
$$\texttt{Kruskal}$$
克鲁斯卡尔算法,一种常见且方便好写的最小生成树算法,利用贪心的思想+并查集维护。
贪心就是按边权从小到大排序,依次处理。如果当前边的两个端点处于同一棵树内,那么就不能加这条边(否则会形成环,不满足生成树的要求),否则将这条边连起来。
判断两点是否处于同一棵树内,这不就是并查集所维护的吗?
排序:\(O(m\log m)\),并查集就看写的是什么了。空间复杂度:\(O(n+m)\)。
$$\texttt{Prim}$$
Prim 是另外一种常见且码量也不算很多的最小生成树算法,主要思想就是维护每个点到生成树内的点的最短距离,每次把距离最小的加入最小生成树。
加入距离最小的点,可以通过暴力查找,也可以用优先队列维护,但它复杂度并没有更优,常数也比 Kruskal 要大,所以一般非必要情况下不会写 Prim。
- 暴力の优势:\(O(n^2+m)\) 的复杂度,对于稠密图来说较为占便宜,例。
- 优化の优势:\(O(m \log m)\) 的复杂度,对于稀疏图来说比较优秀,挺常见。
次小生成树
非严格次小生成树
令图的最小生成树为 \(T\),那么对于每条非树边 \((u,v,w)\),找到 \(T\) 中 \(u\) 到 \(v\) 路径上的边权最大的一条边,把它删掉,再加入 \((u,v,w)\),就可以得到一棵新的生成树,取这些新的生成树中边权之和最小的即可。
维护 \(u,v\) 路径上的边权最大值也很简单,首先预处理,然后每次倍增 LCA 即可。
严格次小生成树
观察原本的算法,为啥它是非严格的呢?因为可能这条路径上的最大值恰好等于 \(w\),此时边权之和没有改变,所以是非严格的。
解决方法也很简单,只要在维护最大值时顺便维护一个严格次大值,当最大值等于 \(w\) 时则用严格次大值来替换。
细节不少,D 了我至少一个小时。
struct MST {
vector<pair<int, int>> g[N];
int fa[N][25], mx[N][25][2], h[N];
void Add (int x, int y, int z) {
g[x].push_back({y, z}), g[y].push_back({x, z});
}
void MkTe (int x) {
h[x]++;
for (auto i : g[x]) {
if (i.first != fa[x][0]) {
fa[i.first][0] = x, mx[i.first][0][0] = i.second, mx[i.first][0][1] = -1, h[i.first] = h[x], MkTe(i.first);
}
}
}
void FAT () {
for (int i = 0; i <= 20; i++) {
mx[0][i][0] = mx[0][i][1] = -1;
}
for (int i = 1; i <= 20; i++) {
for (int j = 1; j <= n; j++) {
fa[j][i] = fa[fa[j][i - 1]][i - 1], mx[j][i][0] = mx[j][i - 1][0], mx[j][i][1] = mx[j][i - 1][1];
if (mx[fa[j][i - 1]][i - 1][0] > mx[j][i][0]) {
mx[j][i][1] = mx[j][i][0], mx[j][i][0] = mx[fa[j][i - 1]][i - 1][0];
} else if (mx[fa[j][i - 1]][i - 1][0] < mx[j][i][0] && mx[fa[j][i - 1]][i - 1][0] > mx[j][i][1]) {
mx[j][i][1] = mx[fa[j][i - 1]][i - 1][0];
}
if (mx[fa[j][i - 1]][i - 1][1] < mx[j][i][0] && mx[fa[j][i - 1]][i - 1][1] > mx[j][i][1]) {
mx[j][i][1] = mx[fa[j][i - 1]][i - 1][1];
}
}
}
}
pair<int, int> lca (int x, int y) {
if (h[x] > h[y]) {
swap(x, y);
}
pair<int, int> xm = {-1, -1};
for (int i = 20; i >= 0; i--) {
if ((h[y] - h[x]) >> i) {
if (mx[y][i][0] > xm.first) {
xm.second = xm.first, xm.first = mx[y][i][0];
} else if (mx[y][i][0] < xm.first && mx[y][i][0] > xm.second) {
xm.second = mx[y][i][0];
}
if (mx[y][i][1] < xm.first && mx[y][i][1] > xm.second) {
xm.second = mx[y][i][1];
}
y = fa[y][i];
}
}
if (x == y) {
return xm;
}
for (int i = 20; i >= 0; i--) {
if (fa[x][i] != fa[y][i]) {
if (mx[y][i][0] > xm.first) {
xm.second = xm.first, xm.first = mx[y][i][0];
} else if (mx[y][i][0] < xm.first && mx[y][i][0] > xm.second) {
xm.second = mx[y][i][0];
}
if (mx[y][i][1] < xm.first && mx[y][i][1] > xm.second) {
xm.second = mx[y][i][1];
}
if (mx[x][i][0] > xm.first) {
xm.second = xm.first, xm.first = mx[x][i][0];
} else if (mx[x][i][0] < xm.first && mx[x][i][0] > xm.second) {
xm.second = mx[x][i][0];
}
if (mx[x][i][1] < xm.first && mx[x][i][1] > xm.second) {
xm.second = mx[x][i][1];
}
y = fa[y][i], x = fa[x][i];
}
}
if (mx[y][0][0] > xm.first) {
xm.second = xm.first, xm.first = mx[y][0][0];
} else if (mx[y][0][0] < xm.first && mx[y][0][0] > xm.second) {
xm.second = mx[y][0][0];
}
if (mx[y][0][1] < xm.first && mx[y][0][1] > xm.second) {
xm.second = mx[y][0][1];
}
if (mx[x][0][0] > xm.first) {
xm.second = xm.first, xm.first = mx[x][0][0];
} else if (mx[x][0][0] < xm.first && mx[x][0][0] > xm.second) {
xm.second = mx[x][0][0];
}
if (mx[x][0][1] < xm.first && mx[x][0][1] > xm.second) {
xm.second = mx[x][0][1];
}
return xm;
}
} mst;