题面
题目意思
n n n个点, m m m条边,可能有重边,求每次删除一棵最小生成树,直到不连通为止,记录每条边是第几次操作删除的,如果不会被删除则记为 − 1 -1 −1,边的权值为给出的边的编号。
题目思路
二分+并查集
如果一条边会被删除,那么这条边可以在 1 − ⌊ m ( n − 1 ) ⌋ 1 - \lfloor\frac{m}{(n-1)}\rfloor 1−⌊(n−1)m⌋次之内删除,因为每次会删除 n − 1 n-1 n−1条边,根据最小生成树的定义,我们一定是选择权值最小的边先进行连接,那么我们就一定会选择前面权值较小的边先进行连边,选择其在哪次操作内被连上,又因为连边具有二段性,前面操作有相连,后面操作无相连,就具有二段性,我们将在那一轮连上,但如果于 ⌊ m ( n − 1 ) ⌋ \lfloor\frac{m}{(n-1)}\rfloor ⌊(n−1)m⌋,那肯定是剩下来的边,不会连接,标记一下,最后对每一条边判断一下在哪一次操作被选上,并且那次操作删除了 n − 1 n-1 n−1条边
感悟
二分拓展比较巧妙,利用第几次来快速判断是否被删除。
#include <bits/stdc++.h>
using namespace std;
const int N = 101000;
vector<int> fa[N];
int n, m;
int find(int x, int y){
if(x != fa[x][y]){
fa[x][y] = find(fa[x][y], y);
}
return fa[x][y];
}
void merge(int u, int v, int c){
fa[find(u, c)][c] = find(v, c);
}
void solve(){
cin >> n >> m;
for(int i = 1; i <= n; i ++) fa[i].clear();
int c = m / (n - 1);
for(int i = 1; i <= n; i ++){
for(int j = 0; j < c; j ++) fa[i].push_back(i);
}
vector<int> ans(m + 1), cnt(m + 1);
for(int i = 1; i <= m; i ++){
int u, v;
cin >> u >> v;
int l = 0, r = c;
while(l < r){
int mid = l + r >> 1;
if(find(fa[u][mid], mid) != find(fa[v][mid], mid)) r = mid;
else l = mid + 1;
}
//cout << r << "\n";
if(r < c){
merge(u, v, r);
}
cnt[r] ++;
ans[i] = r;
}
for(int i = 1; i <= m; i ++){
if(ans[i] == c || cnt[ans[i]] != n - 1) cout << "-1";
else cout << ans[i] + 1;
cout << " \n"[i == m];
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int _ = 1;
cin >> _;
while(_ --){
solve();
}
return 0;
}
/*
*/