CF1108F 题解
题目传送门
题目大意:
给定一个有 \(n\) 个点,\(m\) 条边的无向连通图,每次操作是将某条边的权值加一,求最少操作次数使得该无向连通图的最小生成树唯一。
修改操作不能改变 MST 的权值和。
思路:
修改操作不能改变 MST 的权值和。 这句话很关键,它告诉我们只能在最开始的最小生成树上考虑,否则情况就很冗杂了。
所以我们肯定得先求出原图的最小生成树。
不难想到当最小生成树不唯一时,会有其他非树边去替换最小生成树上的边,那么什么时候可以替换呢?
假设我们已经求出了原图的一颗最小生成树,并且扫描到一条非树边 $ e = (a, b, w)$,那么它必定和最小生成树上 \(a,b\) 之间的路径构成一个环,在这个环上,断掉任意一条边,它一定还是一棵生成树。
设这条环上权值最大的树边为 \(e'\),如果在这条环上的树边的最大权值等于了这条非树边,即 \(e'_w = e_w\),那么断掉 \(e\) 或断掉 \(e'\) 都是可行方案了,即这时候 \(e\) 可以替换 \(e'\),这就会使最小生成树不唯一。
为了防止这种情况发生,我们只需要将 \(e_w + 1\) 即可,就算 \(e'\) 可能会有很多条,但因为 \(e_w\) 已经大于环上所有的边权了,所以无法替换。
求树上两点之间的最大边权可以用倍增 LCA 解决。
综上所述,我们先用 \(\texttt{kruskal}\) 算法求出最小生成树,顺便将最小生成树的边存下来,再 bfs 预处理一下最小生成树的 LCA,然后依次扫描每条非树边,若满足上述性质,则答案加一。
时间复杂度为 \(O(n\log n)\)。(假设 \(n,m\) 同级)
\(\texttt{Talk is cheap, show you the code.}\)
#include <queue>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010;
int n, m;
int h[N], e[N << 1], ne[N << 1], w[N << 1], idx;
struct node{
int a, b, w;
bool type;
bool operator< (const node &o) const {
return w < o.w;
}
}edges[N];
int p[N];
int dep[N], f[N][22], T;
int maxd[N][22]; //maxd[i][j] 表示节点 i 到它的 2^j 辈祖先这条路径上的最大边权
void add(int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int find(int x) {
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
void bfs(int s) {
queue<int> q;
q.push(s);
dep[s] = 1;
while(!q.empty()) {
int t = q.front();
q.pop();
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(dep[j]) continue;
dep[j] = dep[t] + 1;
f[j][0] = t;
maxd[j][0] = w[i];
for(int k = 1; k <= T; k++) {
f[j][k] = f[f[j][k - 1]][k - 1];
maxd[j][k] = max(maxd[j][k - 1], maxd[f[j][k - 1]][k - 1]);
}
q.push(j);
}
}
}
int lca(int x, int y) {
int res = -0x3f3f3f3f;
if(dep[x] > dep[y]) swap(x, y);
for(int i = T; i >= 0; i--)
if(dep[f[y][i]] >= dep[x]) {
res = max(res, maxd[y][i]);
y = f[y][i];
}
if(x == y) return res;
for(int i = T; i >= 0; i--)
if(f[x][i] != f[y][i]) {
res = max(res, maxd[x][i]);
res = max(res, maxd[y][i]);
x = f[x][i], y = f[y][i];
}
res = max(res, maxd[x][0]);
res = max(res, maxd[y][0]);
return res;
}
int main() {
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
int a, b, w;
for(int i = 1; i <= m; i++) {
scanf("%d%d%d", &a, &b, &w);
edges[i] = {a, b, w};
}
sort(edges + 1, edges + m + 1);
int sum = 0;
for(int i = 1; i <= n; i++) p[i] = i;
int ori = 0; //随便找一个起点
for(int i = 1; i <= m; i++) {
a = edges[i].a, b = edges[i].b, w = edges[i].w;
int x = find(a), y = find(b);
if(x != y) {
p[x] = y;
add(a, b, w), add(b, a, w);
edges[i].type = true; //树边标记一下
++sum;
ori = a;
}
if(sum == n - 1) break;
}
T = (int)log2(n);
bfs(ori);
int res = 0;
for(int i = 1; i <= m; i++)
if(!edges[i].type) { //如果是非树边
int tmp = lca(edges[i].a, edges[i].b);
if(edges[i].w == tmp) ++res; //满足上述性质则累加答案
}
printf("%d", res);
return 0;
}