P2619 [国家集训队]Tree I(二分+Kruscal)
题意
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 needneed 条白色边的生成树。
题目保证有解。
输入格式
第一行 V,E,need 分别表示点数,边数和需要的白色边数。
接下来 E 行,每行 s,t,c,col 表示这边的端点(点从 0 开始标号),边权,颜色(0 白色 1 黑色)。
输出格式
一行,表示所求生成树的边权和。
样例
input
2 2 1
0 1 1 1
0 1 2 0
output
2
思路
Kruscal求最小生成树,是通过边权从小到大构成MST,那么很显然如果边权越小,那么这条边就越容易被用到,如果我们手动给所有白边都加上一个权值x,那么新的MST中白边的数量显然会跟着x的单调递增而单调递减。于是,我们二分答案这个x就行了。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
//#pragma GCC optimize(3)
#define pb push_back
#define is insert
#define PII pair<int,int>
#define show(x) cerr<<#x<<" : "<<x<<endl;
//mt19937 mt19937random(std::chrono::system_clock::now().time_since_epoch().count());
//ll getRandom(ll l,ll r){return uniform_int_distribution<ll>(l,r)(mt19937random);}
const ll INF=0x3f3f3f3f;//2147483647;
const int N=5e4+50,M=1e5+50;
const ll mod=998244353;
int n,m,k;
struct node {
int u,v,val,col;
}e[M],tmp[M];
bool cmp(node a,node b){
if(a.val==b.val){
return a.col<b.col;
}
return a.val<b.val;
}
int fa[N];
int find(int x){
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void init(){
for(int i=0;i<=n;i++){
fa[i]=i;
}
}
int ans=INF;
int ans1=INF;
int check(int x){
//cout<<x<<"\n";
init();
for(int i=1;i<=m;i++){
tmp[i]=e[i];
if(tmp[i].col==0){
tmp[i].val+=x;
}
}
sort(tmp+1,tmp+1+m,cmp);
int res=0;
int tot=0;
for(int i=1;i<=m;i++){
int a=find(tmp[i].u),b=find(tmp[i].v);
if(a!=b){
if(tmp[i].col==0){
tot++;
}
fa[a]=b;
res+=tmp[i].val;
}
}
//cout<<res<<" "<<tot<<"\n";
if(tot==k){
ans1=min(ans1,res-k*x);
}
if(tot>k){
ans=res-k*x;
//ans=min(ans,res-k*x);
return 1;
}
return 0;
}
void solve() {
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].val>>e[i].col;
}
int l=-110,r=110;
while(l<r){
int mid=l+r>>1;
if(check(mid)){
l=mid+1;
}
else r=mid;
}
cout<<min(ans1,ans);
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int __=1;//cin>>__;
while(__--){
solve();
}
return 0;
}