BZOJ 2654 tree

Description

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有(need)条白色边的生成树。
题目保证有解。

Input

第一行(V,E,need)分别表示点数,边数和需要的白色边数。
接下来(E)
每行(s,t,c,col)表示这边的端点(点从(0)开始标号),边权,颜色((0)白色(1)黑色)。

Output

一行表示所求生成树的边权和。

Sample Input

2 2 1
0 1 1 1
0 1 2 0

Sample Output

2

HINT

(0:V le 10)
(1,2,3:V le 15)
(0,...,19:V le 50000,E le 100000)
所有数据边权为([1,100])中的正整数。

我听lyp说,这题是clj出的。lyp讲课的时候把这题作为例题跟我们讲解。
其实做法很简单,二分一个值(mid),然后将所有白边的边权都减去(mid)再来做最小生成树。但是就是很难YY出来。
代码有细节需要处理,如果处理不好,可以参考参考我写的程序。

#include<algorithm>
#include<cstdio>
#include<cstdlib>
using namespace std;

#define maxn (100010)
int V,E,need,father[maxn],ans;
struct EDGE
{
	int s,t,c,col;
	inline void read() { scanf("%d %d %d %d",&s,&t,&c,&col); ++s; ++t; }
	inline EDGE dec(int k) { if (col) return *this; return (EDGE){s,t,c-k,col}; }
	friend inline bool operator <(const EDGE &x,const EDGE &y) { return x.c != y.c?x.c < y.c:x.col < y.col; }
}bac[maxn],edge[maxn];

inline int find(int a) { if (father[a] != a) father[a] = find(father[a]); return father[a]; }

inline void init() { for (int i = 1;i <= V;++i) father[i] = i; }

inline bool check(int key)
{
	for (int i = 1;i <= E;++i) edge[i] = bac[i].dec(key);
	sort(edge+1,edge+E+1);
	int cnt = 0,tot = 0,sum = 0;
	init();
	for (int i = 1;i <= E;++i)
	{
		int r1 = find(edge[i].s),r2 = find(edge[i].t);
		if (r1 != r2)
		{
			++cnt; tot += edge[i].col == 0; sum += edge[i].c;
			father[r1] = r2;
		}
		if (cnt == V-1) break;
	}
	if (tot == need) printf("%d",sum+key*need),exit(0);
	else if (tot < need) return false;
	ans = sum+key*need;
	return true;
}

int main()
{
	freopen("2654.in","r",stdin);
	freopen("2654.out","w",stdout);
	scanf("%d %d %d",&V,&E,&need);
	for (int i = 1;i <= E;++i) bac[i].read();	
	int l = -110,r = 110,mid;
	while (l <= r)
	{
		mid = (l + r) >> 1;
		if (check(mid)) r = mid - 1;
		else l = mid + 1;
	}
	printf("%d",ans);
	fclose(stdin); fclose(stdout);
	return 0;
}
原文地址:https://www.cnblogs.com/mmlz/p/4314929.html