
题目分析:
- 很容易想到将边排序后先选白边,选完后又选黑边,但这个贪心是错误的,因为白边的选择会影响黑边的选择,再影响总的选择
- 二分一个
x
x
x,将
x
x
x加在白边的权值上,因为Kruskal是通过边权从小到大贪心,这样我们就可以控制选择的白边的数量
- 注意:如果在你的二分过程中如果给白边加上mid,你得到的白边数比need大。给白边加上mid+1,你得到的白边比need小。这种情况看似没法处理。
但是考虑一下克鲁斯卡尔的加边顺序。可以发现如果出现这种情况,一定是有很多相等的白边和黑边。因为数据保证合法。所以我们可以把一些白边替换成黑边。所以我们要在白边数>=need的时候跟新答案 - 即不能在check函数中更新答案
- 真正的正解–wqs二分
Code:
#include <bits/stdc++.h>
using namespace std;
#define maxn 51000
#define maxm 100100
int n,m,ne,f[maxn],ans,mind=999999999;
struct edge {
int u,v,w,pd;
}e[maxm];
inline void init_() {
freopen("a.txt","r",stdin);
}
inline int read_() {
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return x*f;
}
int find_(int x) {
if(f[x]==x) return x;
else return f[x]=find_(f[x]);
}
inline void clean_() {
for(int i=0;i<=n+1;++i) f[i]=i;
}
bool cmp_(edge aa,edge bb) {
if(aa.w==bb.w) return aa.pd<bb.pd;
return aa.w<bb.w;
}
int check_(int x) {
for(int i=1;i<=m;++i) {
if(!e[i].pd) e[i].w+=x;
}
clean_();
sort(e+1,e+m+1,cmp_);
int cnt=0,tot=0;
ans=0;
for(int i=1;i<=m;++i) {
int fx=find_(e[i].u),fy=find_(e[i].v);
if(fx!=fy) {
f[fx]=fy;
ans+=e[i].w;
++cnt;
if(!e[i].pd) ++tot;
}
if(cnt==(n-1)) break;
}
for(int i=1;i<=m;++i) {
if(!e[i].pd) e[i].w-=x;
}
if(tot>=ne) return 1;
return 0;
}
void readda_() {
n=read_();m=read_();ne=read_();
for(int i=1;i<=m;++i) {
e[i].u=read_();e[i].v=read_();e[i].w=read_();e[i].pd=read_();
}
int l=-1111,r=1111;
while(l<=r) {
int mid=(l+r)>>1;
if(check_(mid)) {
l=mid+1;
mind=ans-ne*mid;
}
else r=mid-1;
}
printf("%d",mind);
}
int main() {
init_();
readda_();
return 0;
}