洛谷 P2619 【[国家集训队2]Tree I】

参考了这篇题解,真的写的很好。

我们直接跑最小生成树时,会出现三种情况:

  • 白边多了
  • 白边少了
  • 白边刚刚好

对于最后一种情况,很好办,但是其他两种怎么办呢?

我们回顾\(kruskal\)的算法思想,他的核心就是贪心,每次选最小的边加入,那么我们是否可以改变一下白边的优先级,让他每次加入的白边数恰好为\(need\)呢?可以的。我们给白边加上一个值,让他变大,再进行排序,就可以实现控制加入白边的数量了,最后减去即可。

问题又来了,该加多少呢?枚举\(-100\)\(100\)?这样做显然花费太多时间了。考虑单调性,当我们加的数越多,选入的白边就越少,而加的越少,选入的白边越多,这样我们就可以二分了,枚举加的数,跑二分,最后输出。

代码:

#include 
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;
}

你可能感兴趣的:(洛谷 P2619 【[国家集训队2]Tree I】)