Luogu P14080 [GESP202509 八级] 最小生成树 题解 [ 蓝 ] [ 最小生成树 ] [ 并查集 ] [ 树链剖分 ] [ 次小生成树 ] [ 倍增 ]
最小生成树:板、典、原。
不难想到我们可以先找出原图的 MST,然后对于不在 MST 上的边,删掉它也不会影响答案。因此我们只需要考虑在 MST 上的边删掉有什么影响即可。
假设当前被删掉的边为 \(e\),两个端点为 \(u, v\),那么如果要使得 MST 继续保持连通,则添加的新边 \((a, b)\) 需要满足 \(e\) 在原 MST 里 \(\bm{a \to b}\) 的路径上。在此基础上,找到这些边的边权最小值,就可以求出新的 MST 了。
考虑如何找到满足要求的边权最小值,显然我们可以让非 MST 边去反过来贡献树边,给 \(a\to b\) 路径上的每一条边取一个最小值。最后删除边 \(e\) 的时候只需查询 \(e\) 上被覆盖的边权最小值即可。显然可以用树剖实现,时间复杂度 \(O(n\log^2 n)\)。
但是这是 GESP,不能考树剖,于是我们换一种简单的思路来实现。注意到这是个树链覆盖的操作,且最后覆盖的值由最小值决定。所以可以将边按权值排序之后,从小到大依次覆盖未被覆盖过的 MST 树边,这个显然可以用并查集进行维护,具体维护的是每个节点未被覆盖的深度最深的祖先(包括自己)。因为均摊下来每条边最多只会覆盖一次,所以最后的时间复杂度为 \(O(n\log n)\)。瓶颈在于排序。
#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 100005, M = 400005;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n, m;
ll res = 0, ans[M], ysans[M];
vector<pi> g[N];
bool inmst[M];
struct Edge{
int u, v, id;
ll w;
bool operator < (const Edge & t) const{
return (w < t.w);
}
}e[M];
struct DSU{
int fa[N];
void init()
{
for(int i = 1; i <= n; i++) fa[i] = i;
}
int findf(int x)
{
if(fa[x] != x) fa[x] = findf(fa[x]);
return fa[x];
}
void combine(int x, int y)
{
int fx = findf(x), fy = findf(y);
fa[fx] = fy;
}
}dsu1, dsu2;
int dep[N], bleg[N], fax[N];
void dfs(int u, int fa)
{
dep[u] = dep[fa] + 1;
fax[u] = fa;
for(auto eg : g[u])
{
int v = eg.fi, eid = eg.se;
if(v == fa) continue;
dfs(v, u);
bleg[v] = eid;
}
}
void update(int u, int v, ll w)
{
u = dsu2.findf(u), v = dsu2.findf(v);
while(u != v)
{
if(dep[u] < dep[v]) swap(u, v);
ans[bleg[u]] = w;
dsu2.combine(u, fax[u]);
u = dsu2.findf(u);
}
}
int main()
{
//freopen("sample.in", "r", stdin);
//freopen("sample.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
e[i] = {u, v, i, w};
}
sort(e + 1, e + m + 1);
memset(ans, -0x3f, sizeof(ans));
dsu1.init();
dsu2.init();
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v, w = e[i].w;
if(dsu1.findf(u) != dsu1.findf(v))
{
dsu1.combine(u, v);
res += w;
g[u].push_back({v, i});
g[v].push_back({u, i});
inmst[i] = 1;
}
}
dfs(1, 0);
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v, w = e[i].w;
if(!inmst[i]) update(u, v, w);
}
for(int i = 1; i <= m; i++)
{
int u = e[i].u, v = e[i].v, w = e[i].w, qid = e[i].id;
if(!inmst[i]) ysans[qid] = res;
else ysans[qid] = res - w + ans[i];
}
for(int i = 1; i <= m; i++) cout << (ysans[i] < 0 ? -1 : ysans[i]) << "\n";
return 0;
}
另一种做法好像是类似次小生成树,用倍增去做,但是我不会。

浙公网安备 33010602011771号