给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 need条白色边的生成树。
题目保证有解。https://www.luogu.com.cn/problem/P2619
我们很容易知道如何求一个 最小权 的生成树
但是我们无法保证题目中所给的数据跑出的最小生成树恰好有 need 条边
奇妙的想法是,我们可以通过修改边的权值来修改跑出的最小生成树,我们让所有的白边都加上或者减去一个 权值 q
从而得到了各种最小生成树
需要注意的是
最小生成树不唯一
也就是说相同的总权值下,有不同的画法
不同的画法意味着可能造成不同的 need 值的选择
由于我们需要的是某种满足条件的选法
而不是某个最小的权值
所以我们在对于白边和黑边的选择顺序不应该是随意的。
我们让相同权值的情况下 在 Kruskal 的优先级中 白边大于黑边
我们在二分或者枚举q的过程中,如果保证有解的话,就一定存在一个q值,让最小生成树中的白边的个数恰好是need。
而我们在实际的选择中 可能 选择的优先级不是简单的 白边优先于 黑边
例如一个 14点是树,需要3条白边,而选择增加q权值的情况下,有13条白边被选,选q + 1的权值的情况下有 0 条白边被选
我们就可以知道,当need = 13的时候 如果不是单纯的白边优先黑边选择,我们只选择 3 条白边,
结论:
每一个Q值将会对应一个Ans
但由于最小生成树不唯一
每一个Ans可能对应着多种局面,即多种need
而这个need的范围是 :优先选白边的need 1
到优先选黑边的need 2
[need1,need2]
#include <bits/stdc++.h>
//本题的cost只有100,二分与枚举的时间复杂度差不多
using namespace std;
const int Mn = 2e5 + 5;
int fa[Mn];
struct node{
int to,from,cost;
int col;
}Edge[Mn];
int cnt = 0;
inline void Adde(int a,int b ,int c,int d){
Edge[++cnt].from = a;
Edge[cnt].to = b;
Edge[cnt].cost = c;
Edge[cnt].col = d;
}
bool cmp1(node a,node b){
if(a.cost == b.cost) return a.col < b.col;
return a.cost < b.cost;
}
bool cmp2(node a,node b){
if(a.cost == b.cost) return a.col > b.col;
return a.cost < b.cost;
}
int Find(int a){
if(a == fa[a]) return a;
return fa[a] = Find(fa[a]);
}
int n,m,need;
int t1,t2;
int Kruskal(int q){
for(int i = 1;i <= m;i ++) if(!Edge[i].col) Edge[i].cost += q;
t1 = t2 = 0;
for(int i = 0;i < n;i ++) fa[i] = i;
int c = 0;
int now = 0;
sort(Edge + 1,Edge + 1 + m,cmp1);
int a,b;
t1 = t2 = 0;
while(c != n - 1){
a = Find(Edge[++now].from);
b = Find(Edge[now].to);
if(a == b) continue;
c ++;
fa[a] = b;
if(Edge[now].col == 0) t1 ++;
}
int ans = 0;
c = 0;
now = 0;
sort(Edge + 1,Edge + 1 + m,cmp2);
for(int i = 0;i < n;i ++) fa[i] = i;
while(c != n - 1){
a = Find(Edge[++now].from);
b = Find(Edge[now].to);
if(a == b) continue;
c ++;
fa[a] = b;
if(Edge[now].col == 0)t2 ++;
ans += Edge[now].cost;
}
for(int i = 1;i <= m;i ++) if(Edge[i].col == 0) Edge[i].cost -= q;
return ans;
}
int main(){
scanf("%d%d%d",&n,&m,&need);
int a,b;
int c,d;
for(int i = 1;i <= m;i ++){
scanf("%d%d%d%d",&a,&b,&c,&d);
Adde(a,b,c,d);
}
int ans = 0;
int l = -101,r = 101;
int mid;
while(l <= r){
mid = (l + r) >> 1;
int t = Kruskal(mid);
if(t1 >= need && t2 <= need){
ans = t - (need * mid);
break;
}else{
if(t2 >= need){
l = mid + 1;
}else r = mid - 1;
}
}
for(int i = -111;i <= 111;i ++){
int t = Kruskal(i);
if(t1 >= need && t2 <= need){
ans = t - (need * i);
break;
}
}
printf("%d",ans);
return 0;
}