题目链接:https://codeforces.com/problemset/problem/362/D
题目大意:
有 n 个城市,它们之中有 m 座桥(双向)相连(城市可以孤立),要你新建 p 座桥,使得城市能恰好划分为 q 个区域。同属一个区域内的城市必定可达,若两个城市区域不同则不可达。若新建一座桥使得两个区域合并为一,花费为min(1e9, S) + 1.其中S为这两个区域原本含有的桥的权值之和。若在只在一个区域内的城市建桥,花费1000.注意:允许重边,不得有自环。
解题思路:
怎么知道当前城市属于哪个区域呢?我们可以用并查集或者连通分量分解给城市打标号。遂计算每个区域的桥的权值之和。先贪心地把区域数减少至q。若建p座桥仍无法完成任务,输出NO,否则,如果桥有富于,随意挑出一个点数大于2的连通分量,随意挑出其中两点连边,满足题意的数量即可。需要注意的是,当我们要合并一些区域时,没有必要把两个连通分量合并,如果连通分量点数小于2,加一点即可,若大于2无需操作。这样做的目的也是为了在至少有两点能在同一个区域内建边(就是错了这,比赛刚结束才改好,气煞我也!)。
代码如下:
# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAX_V = 1e5 + 5;
const ll inf = 1e9;
ll V, m, p, q, x, y, c;
struct node{
ll to, cost;
};
vector <node> G[MAX_V];
vector <ll> rG[MAX_V];
vector <ll> vs;
vector <ll> SCC[MAX_V];
bool used[MAX_V];
ll cmp[MAX_V];
typedef pair <ll, ll> P;
vector <P> ans;
priority_queue <P, vector<P>, greater<P> > que;
void add_edge(ll from, ll to, ll cost){
G[from].push_back({to, cost});
G[to].push_back({from, cost});
rG[to].push_back(from);
rG[from].push_back(to);
}
void dfs(ll v){
used[v] = true;
for(ll i = 0; i < G[v].size(); ++i)
if(!used[G[v][i].to]) dfs(G[v][i].to);
vs.push_back(v);
}
void rdfs(ll v, ll k){
used[v] = true;
cmp[v] = k;
for(ll i = 0; i < rG[v].size(); ++i)
if(!used[rG[v][i]]) rdfs(rG[v][i], k);
}
ll scc(){
memset(used, 0, sizeof(used));
vs.clear();
for(ll v = 1; v <= V; v++){
if(!used[v]) dfs(v);
}
memset(used, 0, sizeof(used));
ll k = 0;
for(ll i = vs.size()-1; i >= 0; --i)
if(!used[vs[i]]) rdfs(vs[i], k++);
return k;
}
int main(){
std::ios::sync_with_stdio(false);
cin >> V >> m >> p >> q;
for(ll i = 0; i < m; ++i){
cin >> x >> y >> c;
add_edge(x, y, c);
}
ll res = scc();
for(ll i = 1; i <= V; ++i) //点的标号从1开始
SCC[cmp[i]].push_back(i); //SCC放连通分量的点,连通序号从0开始
while(!que.empty()) que.pop();
for(ll i = 0; i < res; ++i){ //i是连通分量序号
ll sum = 0;
for(ll j = 0; j < SCC[i].size(); ++j){
for(ll k = 0; k < G[SCC[i][j]].size(); ++k){
sum += G[SCC[i][j]][k].cost;
}
}
que.push({sum / 2, i}); //i, second是强连通序号
//tot[i] = sum / 2;
}
while(res > q){
if(p <= 0) break;
P p1 = que.top(); que.pop();
P p2 = que.top(); que.pop();
que.push({p1.first + p2.first + min(inf, p1.first + p2.first) + 1, p1.second});
if(SCC[p1.second].size() == 1) SCC[p1.second].push_back(SCC[p2.second][0]); //就是这里少了会出错,细节!!!
if(SCC[p2.second].size() == 1) SCC[p2.second].push_back(SCC[p1.second][0]);
ans.push_back({SCC[p1.second][0], SCC[p2.second][0]});
res--;
p--;
}
if(res != q)
cout << "NO" << endl; //桥少了不够建
else{ //桥多了,但是已经满足q区域
ll label = -1;
for(ll i = 0; i < res; ++i){ //有没有点数大于2的分量
if(SCC[i].size() > 1){
label = i;
break;
}
}
if(label == -1 && p) cout << "NO" << endl; //全是单点但是还要建桥
else{
cout << "YES" << endl;
for(ll i = 0; i < ans.size(); ++i)
cout << ans[i].first << ' ' << ans[i].second << endl;
while(p){
cout << SCC[label][0] << ' ' << SCC[label][1] << endl;
p--;
}
}
}
return 0;
}