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