题目传送门
【题目描述】
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 need 条白色边的生成树。题目保证有解。
【输入】
第一行 V,E,need 分别表示点数,边数和需要的白色边数。
接下来 E 行,每行 s,t,c,col 表示这边的端点(点从 0 开始标号),边权,颜色(0 白色,1 黑色)。
【输出】
一行表示所求生成树的边权和。
【输入样例】
2 2 1 0 1 1 1 0 1 2 0
【输出样例】
2
【提示】
数据范围:
对于所有数据,V≤5×104,E≤105 ,边权为 [1,100] 中的正整数。
首先,这道题很明确地告诉我们要写最小生成树.这里就用kruskal来写.
题目要求need条白边,有限定条件,所以不能写裸的.
那么先挑出need条最短的边,再从黑边里面挑组成最小生成树不就行了?
这样还是不行,因为这样它就干扰了原来的最小生成树,不能保证最优解.
因为kruskal算法的根本是边的权值要单调递增.所以我们可以从这里下手,把每一条白边加上一个值,改变每一条边的排列顺序,最后计算答案时就只需要减去原来加上的值就可以了.由于我们不知道要加上多少,所以我们可以用二分答案来做每一次的判断来得到答案.
注意:如果两条边权值相等,白边的优先级要高一些.
代码如下
#include<bits/stdc++.h> #define maxn 50005 using namespace std; int n,m,p,father[maxn],ans,sum,cnt,white; struct node{ int u,v,dis,c; }edge[maxn*4]; inline bool cmp(node x,node y) { if(x.dis==y.dis) return x.c<y.c;//权值相等白边优先级高 return x.dis<y.dis; } inline int find(int x) { if(father[x]!=x) father[x]=find(father[x]); return father[x]; } inline bool check(int x)//二分答案+kruskal { for(int i=0;i<=n;i++) father[i]=i; for(int i=1;i<=m;i++) { if(!edge[i].c) edge[i].dis+=x;//加上x的值 } sort(edge+1,edge+m+1,cmp); cnt=0,white=0,sum=0; for(int i=1;i<=m;i++)//略作修改的kruskal模板 { if(cnt==n-1) break; int x=find(edge[i].u),y=find(edge[i].v); if(x==y) continue; father[x]=y; cnt++; sum+=edge[i].dis; if(!edge[i].c) white++; } for(int i=1;i<=m;i++) { if(!edge[i].c) edge[i].dis-=x;//还原 } if(white>=p) return 1; return 0; } int main() { scanf("%d%d%d",&n,&m,&p); for(int i=1;i<=m;i++) scanf("%d%d%d%d",&edge[i].u,&edge[i].v,&edge[i].dis,&edge[i].c); int l=-101,r=101,mid; while(l<=r) { mid=l+r>>1; if(check(mid)) { l=mid+1; ans=sum-p*mid; } else r=mid-1; } printf("%d",ans); return 0; }