参考了这篇题解,真的写的很好。
我们直接跑最小生成树时,会出现三种情况:
- 白边多了
- 白边少了
- 白边刚刚好
对于最后一种情况,很好办,但是其他两种怎么办呢?
我们回顾(kruskal)的算法思想,他的核心就是贪心,每次选最小的边加入,那么我们是否可以改变一下白边的优先级,让他每次加入的白边数恰好为(need)呢?可以的。我们给白边加上一个值,让他变大,再进行排序,就可以实现控制加入白边的数量了,最后减去即可。
问题又来了,该加多少呢?枚举(-100)至(100)?这样做显然花费太多时间了。考虑单调性,当我们加的数越多,选入的白边就越少,而加的越少,选入的白边越多,这样我们就可以二分了,枚举加的数,跑二分,最后输出。
代码:
#include <bits/stdc++.h>
using namespace std;
struct node{
int col , w , s , t;
};
int n , m , need , tot , p , ans , now;
int fa[50010] , vis[200010];
node e[200010];
bool cmp(node x , node y){ //排序函数 先边权,后颜色
if(x.w == y.w) return x.col < y.col;
return x.w < y.w;
}
int find(int x){
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
void k(){ //最小生成树
sort(e + 1 , e + m + 1 , cmp);
for(int i = 1; i <= m; i++){
if(now == n - 1) break;
int fx = find(e[i].s) , fy = find(e[i].t);
if(fx == fy) continue;
fa[fx] = fy;
tot += e[i].w;
now++;
if(!e[i].col) p++; //记录白边条数
}
}
int main(){
cin >> n >> m >> need;
for(int i = 1; i <= m; i++){
int x , y , z , c;
cin >> x >> y >> z >> c;
x++ , y++; //0开始输入
e[i].s = x , e[i].t = y , e[i].w = z , e[i].col = c;
}
int l = -100 , r = 100;
while(l <= r){ //二分
int mid = (l + r) / 2;
for(int i = 1; i <= m; i++)
if(!e[i].col) e[i].w += mid; //每一个都加上
for(int i = 1; i <= n; i++) fa[i] = i; //初始化
tot = 0 , p = 0 , now = 0;
k();
if(p >= need){ //看是否加入了合适的白边
l = mid + 1;
ans = tot - need * mid; //更新答案
}else r = mid - 1;
for(int i = 1; i <= m; i++)
if(!e[i].col) e[i].w -= mid; //减去
}
cout << ans;
return 0;
}