严格次小生成树
作者:互联网
严格次小生成树
板子题传送门:luogu P4180
定义
严格次小生成树就是说我们设最小生成树选择的边集是 E M E_M EM,严格次小生成树选择的边集是 E S E_S ES,那么需要满足( v a l u e ( e ) value(e) value(e) 表示这个边的边权):
∑ e ∈ E M v a l u e ( e ) < ∑ e ∈ E S v a l u e ( e ) \sum_{e \in E_M}value(e) < \sum_{e \in E_S}value(e) e∈EM∑value(e)<e∈ES∑value(e)
分析
现在就是说现在给定一个无向图,让你求出这个图的次小生成树(严格次小)。很显然我们能想到一个非常朴素的暴力,枚举这张图的所有生成树,比较所有生成树大小就能得出答案了。但是生成树的数量应该是 O ( n 3 ) O(n^3) O(n3) 级别的(虽然我不会证),所以这样的算法效率显然非常低下。所以我们需要更优秀的算法来解决这个问题。
引理:一定存在一棵严格次小生成树,使得它与某棵最小生成树仅有一条边是不同的。
现在我们考虑如何证明这个引理,我们考虑反证法:假设存在某一棵严格次小生成树(边权和为 S 1 S_1 S1)与最小生成树(边权和为 S S S)有 k k k ( k ≥ 2 k \geq 2 k≥2) 条变的差距。我们如果断开这些边,然后这棵次小生成树树就被拆分成了 k + 1 k + 1 k+1 个不连通的部分。我们再选择其中的 k k k 个部分用最小生成树中的边将它们连接成一个连通块,此时它就显然与最小生成树只有一条边的差距了。然后再选择次小生成树中的边将这两个连通块相连。
我们可以发现,此时新的生成树的边权和 S 0 S_0 S0 应该满足 S < S 0 < S 1 S < S_0 < S_1 S<S0<S1。这就和次小生成树的定义矛盾了,所以我们就证完了。
这是一个非常有用的引理啊,再知道它之后我们就可以继续优化我们的算法了。我们可以先把最小生成树给建出来,然后枚举所有不属于最小生成树边集的边,找到那个不属于最小生成树但属于次小生成树的边我们就做完了。
具体来说就是枚举非树边 v ∉ E M v \notin E_M v∈/EM,然后把这条边加入到树中,这时树上就形成了一个环 (设环的边集为 L L L)。然后我们断掉这个换里面最大的边 u = max e ∈ L , e ≠ v , v a l u e ( e ) ≠ v a l u e ( v ) e u = \max\limits_{e \in L, e \neq v, value(e) \neq value(v)}e u=e∈L,e=v,value(e)=value(v)maxe,就能得到含有 v v v 的生成树中边权最小的生成树了。找到所有这样的生成树将权值取 min \min min 就是答案。
但是如果我们暴力找需要短那条边那效率还是太低了,所以我们考虑继续优化。采用树上倍增的方法,定义 1 1 1 为根节点,处理处一个数组存储每个点向上 2 i 2^i 2i 条边的最大值和次大值,在寻找的时候,通过倍增来维护这个数组,这样就能快速找到需要断掉的边了(找环就是求加进去两个点的 l c a lca lca 然后根据再找值就行了)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define in read()
#define MAXN 400400
#define MAXM 4 * MAXN
#define INFI 0x7f7f7f7f7f7f7f7f
inline int read() {
int x = 0;
char c = getchar();
while(c < '0' or c > '9') c = getchar();
while('0' <= c and c <= '9') {
x = x * 10 + c - '0'; c = getchar();
}
return x;
}
int tot = 0;
int first[MAXN] = { 0 };
int nxt[MAXM] = { 0 };
int to[MAXM] = { 0 };
int value[MAXM] = { 0 };
inline void add(int x, int y, int weight) {
nxt[++tot] = first[x];
first[x] = tot; to[tot] = y;
value[tot] = weight;
}
struct Tedge {
int x, y;
int val, used;
bool operator < (const Tedge &rhs) const {
return val < rhs.val;
}
} edge[MAXM];
int ans = 0;
int n = 0;
int m = 0;
int fa[MAXN] = { 0 };
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void kruskal() {
for(int i = 1; i <= m; i++) {
Tedge e = edge[i];
int x = e.x; int y = e.y;
int fax = find(x); int fay = find(y);
if(fax == fay) continue;
add(x, y, e.val); add(y, x, e.val);
ans += e.val, edge[i].used = 1, fa[fay] = fax;
}
}
int f[MAXN][25] = { 0 }; // 祖先
int mx[MAXN][25] = { 0 }; // 最大值
int mm[MAXN][25] = { 0 }; // 次大值
int dep[MAXN] = { 0 };
void dfs(int x) {
dep[x] = dep[f[x][0]] + 1;
for(int i = 1; i <= 20; i++) {
f[x][i] = f[f[x][i - 1]][i - 1];
if(mx[x][i - 1] == mx[f[x][i - 1]][i - 1]) {
mx[x][i] = mx[x][i - 1];
mm[x][i] = max(mm[f[x][i - 1]][i - 1], mm[x][i - 1]);
}
if(mx[x][i - 1] > mx[f[x][i - 1]][i - 1]) {
mx[x][i] = mx[x][i - 1];
mm[x][i] = max(mx[f[x][i - 1]][i - 1], mm[x][i - 1]);
}
if(mx[x][i - 1] < mx[f[x][i - 1]][i - 1]) {
mx[x][i] = mx[f[x][i - 1]][i - 1];
mm[x][i] = max(mx[x][i - 1], mm[f[x][i - 1]][i - 1]);
}
}
for(int e = first[x]; e; e = nxt[e]) {
int y = to[e];
if(y == f[x][0]) continue;
f[y][0] = x; mx[y][0] = value[e];
dfs(y);
}
}
int queryLca(int x, int y) {
if(dep[y] > dep[x]) swap(x, y);
for(int i = 20; i >= 0; i--) {
if(dep[f[x][i]] >= dep[y]) x = f[x][i];
if(x == y) return x;
}
for(int i = 20; i >= 0; i--) {
if(f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int solve(int x, int y, int w) {
int lca = queryLca(x, y);
int numx = 0; int numm = 0;
for(int i = 20; i >= 0; i--) {
if(dep[f[x][i]] >= dep[lca]) {
if(numx == mx[x][i]) numm = max(numm, mm[x][i]);
if(numx > mx[x][i]) numm = max(numm, mx[x][i]);
if(numx < mx[x][i])
numx = mx[x][i], numm = max(numm, mm[x][i]);
x = f[x][i];
}
if(dep[f[y][i]] >= dep[lca]) {
if(numx == mx[y][i]) numm = max(numm, mm[y][i]);
if(numx > mx[y][i]) numm = max(numm, mx[y][i]);
if(numx < mx[y][i])
numx = mx[y][i], numm = max(numm, mm[y][i]);
y = f[y][i];
}
}
if(w != numx) return ans - numx + w;
else if(numm) return ans - numm + w;
else return INFI;
}
signed main() {
n = in; m = in;
for(int i = 1; i <= m; i++) {
edge[i].x = in; edge[i].y = in;
edge[i].val = in; edge[i].used = 0;
}
sort(edge + 1, edge + m + 1);
for(int i = 1; i <= n; i++) fa[i] = i;
kruskal(); dfs(1);
int res = INFI;
for(int i = 1; i <= m; i++) {
Tedge e = edge[i];
if(!e.used) res = min(res, solve(e.x, e.y, e.val));
}
cout << res << '\n';
return 0;
}
标签:mm,int,max,生成,严格,numm,mx 来源: https://blog.csdn.net/ID246783/article/details/123588521