2654: tree
Time Limit: 30 Sec Memory Limit: 512 MBSubmit: 3077 Solved: 1293
[ Submit][ Status][ Discuss]
Description
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解。
Input
第一行V,E,need分别表示点数,边数和需要的白色边数。
接下来E行,每行s,t,c,col表示这边的端点(点从0开始标号),边权,颜色(0白色1黑色)。
Output
一行表示所求生成树的边权和。
V<=50000,E<=100000,所有数据边权为[1,100]中的正整数。
Sample Input
2 2 1
0 1 1 1
0 1 2 0
0 1 1 1
0 1 2 0
Sample Output
2
HINT
原数据出错,现已更新 by liutian,但未重测---2016.6.24
解析:
二分答案+最小生成树。
做这道有一种错路的思路,即按边的种类为第一关键字,边的权值为第二关键字排序,然后做最小生成树,
先取边need条后再跳过去取黑边。反例很好举,如两点之间相连的白边权值较小但未取到,而黑边权值非常大
,这样得到的答案就不是最优的。
这道题的正解个人感觉有点奇怪。我们考虑让白边变得更重要(不重要),所以可以给白边都加上(减去)
一个值num,我们二分这个值,带入验证,最终的答案为sum-num*need。
代码:
#include <bits/stdc++.h>
using namespace std;
const int Max=100010;
int n,m,k,ans;
int father[50100];
struct shu{int x,y,len,flag;};
shu edge[Max];
inline int get_int()
{
int x=0,f=1;
char c;
for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
if(c=='-') {f=-1;c=getchar();};
for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
return x*f;
}
inline bool comp(const shu &a,const shu &b)
{
return a.len == b.len ? a.flag < b.flag : a.len < b.len;
}
inline int getfather(int v)
{
return father[v]==v ? v :father[v]=getfather(father[v]);
}
inline int check(int num)
{
shu bian[Max];
memcpy(bian,edge,sizeof(edge));
for(int i=1;i<=n;i++) father[i]=i;
for(int i=1;i<=m;i++) if(!bian[i].flag) bian[i].len+=num;
sort(bian+1,bian+m+1,comp);
int s=0,minn=0,x=0;
for(int i=1;i<=m;i++)
{
int fax=getfather(bian[i].x),fay=getfather(bian[i].y);
if(fax != fay)
{
s++;
minn+=bian[i].len;
father[fay]=fax;
if(!bian[i].flag) x++;
}
if(s == n-1) break;
}
if(x >= k) {ans=minn-k*num;return 1;}
else return 0;
}
int main()
{
//freopen("tree.in","r",stdin);
n=get_int();
m=get_int();
k=get_int();
for(int i=1;i<=m;i++)
{
edge[i].x=get_int()+1;
edge[i].y=get_int()+1;
edge[i].len=get_int();
edge[i].flag=get_int();
}
int l=-100,r=100,mid;
while(l ^ r)
{
mid = l + r >> 1;
if (check(mid)) l = mid + 1;
else r = mid;
}
cout<<ans<<"\n";
return 0;
}