洛谷 P4180 【模板】严格次小生成树[BJWC2010]
题目描述
小C最近学了很多最小生成树的算法,Prim算法、Kurskal算法、消圈算法等等。正当小C洋洋得意之时,小P又来泼小C冷水了。小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是EM,严格次小生成树选择的边集是ES,那么需要满足:(value(e)表示边e的权值)
这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。
输入输出格式
输入格式:
第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。
输出格式:
包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)
输入输出样例
说明
数据中无向图无自环; 50% 的数据N≤2 000 M≤3 000; 80% 的数据N≤50 000 M≤100 000; 100% 的数据N≤100 000 M≤300 000 ,边权值非负且不超过 10^9 。
这道题直接说做法吧
首先我们需要知道一个性质 次小生成树的树边只和最小生成树有一边之差 别问我正确性我也不知道为什么 显然求证法...??
所以首先我们先cao一颗最小生成树出来 在考虑到底是哪条非树边替换哪条树边
所以枚举每一条非树边$(u, v)$ 在树上找到连接这两点的树链 寻找该链上的最大值 直接进行替换即可
为什么可以直接替换呢 因为既然他是最小生成树 那条链上的每条边都必然小于等于当前$(u, v)$ 但是会出现一个问题
题目要求的是 严格次小 若是最大值与$val(u, v)$相等 那么替换之后就不是严格次小了 所以要解决这个问题 不仅需要维护链上的最大值 还要维护次大值
若是$val(u, v) == max$ 那么我们就替换次大边即可
可以使用树剖套线段树维护这个 倍增也可以
代码(树剖)
#include <bits/stdc++.h> #define oo 1e18 using namespace std; typedef long long ll; const int N = 1e6 + 6; int fa[N], head[N], nex[2 * N], tov[2 * N], val[2 * N], fat[N]; int tot, dep[N], son[N], size[N], in[N], idc, top[N], seq[N], w[N]; int n, m; bool vis[3 * N]; ll sum = 0; struct edge { int u, v, w; }e[3 * N]; struct segment { int ma1, ma2; }f[4 * N], ans; bool cmp(const edge & a, const edge & b) { return a.w < b.w; } int find_fa(int u) { return u == fat[u] ? u : fat[u] = find_fa(fat[u]); } void add(int u, int v, int w) { tot ++; nex[tot] = head[u]; tov[tot] = v; val[tot] = w; head[u] = tot; } void dfs1(int u, int fat) { fa[u] = fat; size[u] = 1; for(int i = head[u];i;i = nex[i]) { int v = tov[i]; if(v == fat) continue; dep[v] = dep[u] + 1; w[v] = val[i]; dfs1(v, u); size[u] += size[v]; if(size[v] > size[son[u]]) son[u] = v; } } void dfs2(int u, int tp) { in[u] = ++ idc; top[u] = tp; seq[idc] = u; if(son[u]) dfs2(son[u], tp); for(int i = head[u];i;i = nex[i]) { int v = tov[i]; if(v == fa[u]) continue; if(v != son[u]) dfs2(v, v); } } void update(int o) { int ma = max(f[2 * o].ma1, f[2 * o + 1].ma1); f[o].ma1 = ma; f[o].ma2 = max((ma == f[2 * o].ma1 ? f[2 * o].ma2 : f[2 * o].ma1), (ma == f[2 * o + 1].ma1 ? f[2 * o + 1].ma2 : f[2 * o + 1].ma1)); } void build(int o, int l, int r) { if(l == r) { f[o].ma1 = w[seq[l]]; f[o].ma2 = -1; return ;} int mid = l + r >> 1; build(2 * o, l, mid); build(2 * o + 1, mid + 1, r); update(o); } void Init( ) { scanf("%d%d",& n,& m); for(int i = 1;i <= m;i ++) scanf("%d%d%d",& e[i].u, & e[i].v, & e[i].w); sort(e + 1, e + m + 1, cmp); for(int i = 1;i <= n;i ++) fat[i] = i; for(int i = 1;i <= m;i ++) { int fa1 = find_fa(e[i].u), fa2 = find_fa(e[i].v); if(fa1 == fa2) continue; fat[fa2] = fa1; vis[i] = true; sum += e[i].w; add(e[i].u, e[i].v, e[i].w); add(e[i].v, e[i].u, e[i].w); } dfs1(1, 0); dfs2(1, 0); build(1, 1, n); } void change(segment a) { int ma = max(a.ma1, ans.ma1); ans.ma2 = max((ans.ma1 == ma ? ans.ma2 : ans.ma1), (a.ma1 == ma ? a.ma2 : a.ma1)); ans.ma1 = ma; } segment query(int o, int l, int r, int L, int R) { if(l >= L && r <= R) return f[o]; int mid = l + r >> 1; if(R <= mid) return query(2 * o, l, mid, L, R); if(L > mid) return query(2 * o + 1, mid + 1, r, L, R); segment q1 = query(2 * o, l, mid, L, R), q2 = query(2 * o + 1, mid + 1, r, L, R), q3; int ma = max(q1.ma1, q2.ma1); q3.ma1 = ma; q3.ma2 = max((ma == q1.ma1 ? q1.ma2 : q1.ma1), (ma == q2.ma1 ? q2.ma2 : q2.ma1)); return q3; } segment Query(int u, int v) { ans.ma1 = 0, ans.ma2 = -1; while(top[u] != top[v]) { if(dep[top[u]] < dep[top[v]]) swap(u, v); change(query(1, 1, n, in[top[u]], in[u])); u = fa[top[u]]; } if(dep[u] < dep[v]) swap(u, v); if(u != v) change(query(1, 1, n, in[v] + 1, in[u])); return ans; } void Solve( ) { ll Ans = oo; for(int i = 1;i <= m;i ++) { if(vis[i]) continue; segment ma = Query(e[i].u, e[i].v); ll del; if(ma.ma1 == e[i].w) del = ma.ma2; else del = ma.ma1; if(del == -1) continue; Ans = min(Ans, sum - del + 1ll * e[i].w); } printf("%lld\n", Ans); } int main( ) { Init( ); Solve( ); }