Problem
給出n個點、m條邊的無向連通圖,每條邊具有2個邊權,一高一低,我們需要選擇若干條邊,使得圖連通的情況下選擇至少k條較高邊權,輸出選擇的邊中,邊權最大值的最小值,輸出答案的一半(保證偶數)
Slove
假設每條邊只具有1條邊權,答案顯而易見,跑一遍最小生成樹即可,因為最小生成樹就是最小瓶頸樹
但是如果存在較高邊權且需要選至少k個,該怎麼辦呢?
注意到,Kruskal演算法的本質就是貪心,每次選擇最小的邊,如果二點未連通,則用並查集合並,否則跳過,直到選擇n-1條邊
所以此時我們可以將所有邊權都遍歷一遍,當選擇較高邊權時k-1,如果k=還需要選擇的邊時,跳過所有較低邊權......嗎?
不難發現,此時最小生成樹=最小瓶頸樹不再適用,但我們可以按照這種貪心思路,優先選擇k條較高邊權,然後再根據需要選擇剩下的邊權(也包括較高邊權,因為有時某一條邊的較低邊權可能比另一條邊的較高邊權還要高!)。因為k條高階邊始終是要選擇的,且高於低階邊權。
時間複雜度就是Kruskal的時間複雜度,為\(O(nlogn)\)
Code
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdlib>
#include<list>
#include<map>
#include<queue>
#include<stack>
#include<vector>
using namespace std;
int n,k,m,ans;
int f[10005];
struct p{
int st,to,w,idx;
bool type;
};
vector<p> g,road;
bool cmp(p a,p b){
if(a.w!=b.w)
return a.w<b.w;
return a.type>b.type;
}
bool cmp1(p a,p b){
return a.idx<b.idx;
}
int find(int u){
if(f[u]==u)return u;
return f[u]=find(f[u]);
}
bool same(int u,int v){
return find(u)==find(v);
}
bool unit(int u,int v){
if(same(u,v))return false;
f[find(u)]=v;
return true;
}
void kruskal(){
int edge=n-1;
for(int i=0;i<g.size();i++){
if(k>0&&!g[i].type){
continue;
}
if(k==0){
i=0;
k--;
}
if(unit(g[i].st,g[i].to)){
k-=g[i].type;
edge--;
ans=max(ans,g[i].w);
road.push_back(g[i]);
}
if(!edge)break;
}
}
int main(){
cin>>n>>k>>m;
for(int i=1;i<=n;i++)f[i]=i;
for(int i=1,st,to,w1,w2;i<m;i++){
cin>>st>>to>>w1>>w2;
g.push_back({st,to,w1,i,1});
g.push_back({st,to,w2,i,0});
}
sort(g.begin(),g.end(),cmp);
kruskal();
cout<<ans<<endl;
sort(road.begin(),road.end(),cmp1);
for(int i=0;i<road.size();i++){
int type;
if(road[i].type==1)type=1;
else type=2;
cout<<road[i].idx<<" "<<type<<endl;
}
return 0;
}