F - Construct Highway
题目:
给定一张 N 个点 M 条边的图,要求补上剩下的
(
N
−
1
−
M
)
(N - 1 - M)
(N−1−M) 条边,使其每个点的度数满足题目所给定的
d
i
d_i
di,输出可行方案,如无可行方案,则输出
−
1
-1
−1
思路:
一共有
N
N
N 个点
(
N
−
1
)
(N - 1)
(N−1) 条边,说明该图无环,所以所有点度的总和一定为
2
∗
(
n
−
1
)
2 * (n - 1)
2∗(n−1) 否则无解。
显然,对于个剩余度大于
1
1
1 的点,连接一个度恰好为
1
1
1 的点一定是最优方案。
所以我们将每一个度大于
1
1
1 的连通块与 度等于
1
1
1 的连通块分别存在两个
v
e
c
t
o
r
vector
vector 中,为了方便处理出最后的方案,我们将度为
d
[
i
]
(
d
[
i
]
>
1
)
d[i] (d[i]> 1)
d[i](d[i]>1) 的连通块存
d
[
i
]
d[i]
d[i] 个,表示该连通块中可以连接的点有哪些,这样连接一次就使用一个,就可以不用统计某一个点剩余个数。
在将度大于一的连通块使用到度为
1
1
1 的时候,就停止对该连通块的连接,将其放进度为
1
1
1 的数组中,再去使用其他度大于
1
1
1 的连通块。
这样操作之后,对于一个可行方案,最后一定会只剩下两个度为 1 的连通块,这是因为如果此时连通块个数大于
2
2
2 的话就必须要有度大于
1
1
1 的连通块才能让这几个连通块连接在一起;然后再将最后两个点直接连接起来即可。
当然,对于点的连接与判定是否在一个联通块中,以及存储某一个连通块中有哪些点是可用的,这里用并查集维护是比较方便的。
代码+注释:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int n, m;
int fa[N];
int d[N];
vector<int> temp1;
vector<PII> ans;
vector<vector<int>> temp2, v(N);
int find(int u) {
if(fa[u] != u) fa[u] = find(fa[u]);
return fa[u];
}
void merge(int a, int b) {
int xa = find(a), xb = find(b);
if(xa != xb) fa[xa] = xb;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("D:/Cpp/program/Test.in", "r", stdin);
freopen("D:/Cpp/program/Test.out", "w", stdout);
#endif
cin >> n >> m;
int sum = 0;
// cout << 1 << '\n';
for(int i = 1; i <= n; i ++) cin >> d[i], fa[i] = i, sum += d[i];
if(sum != 2 * (n - 1)) {
cout << -1 << '\n';
return 0;
}
while(m --) {
int a, b;
cin >> a >> b;
d[a] --, d[b] --;
merge(a, b);
}
for(int i = 1; i <= n; i ++) {
if(d[i] < 0) {
cout << -1 << '\n';
return 0;
}
int pi = find(i);
for(int j = 1; j <= d[i]; j ++) {
v[pi].push_back(i); //以 pi 为根的树中有 多少个点 i 就能让 i 连接多少次
}
}
//度数为 1 的连通块与度数大于 1 的连通块相连是最优策略,所以将以上分为两组来处理
for(int i = 1; i <= n; i ++) {
if(v[i].size() == 1) temp1.push_back(v[i][0]);
else if(v[i].size() > 1) temp2.push_back(v[i]);
}
// cout << ans.size() << '\n';
for(auto t : temp2) {
//留下一个最后放入 度为1的当中去
for(int i = 0; i < t.size() - 1; i ++) {
if(temp1.empty()) { //如果不存在度为 1, 说明只能连接成环,无法形成一棵树
cout << -1 << '\n';
return 0;
}
merge(t[i], temp1.back());
ans.push_back({t[i], temp1.back()});
temp1.pop_back();
}
temp1.push_back(t.back());
}
//通过上述方式合并之后,一定会剩下一个度为 1 的点被放入了 temp1 中,
//在 temp1 中,每个点都在不同的连通块中并且只能被连接一次,只有剩余 2 个连通块时才会有解
if(temp1.size() != 2) {
cout << -1 << '\n';
return 0;
}
merge(temp1[0], temp1[1]);
ans.push_back({temp1[0], temp1[1]});
//最后检查是否所有点都联通
for(int i = 1; i <= n; i ++) {
if(find(i) != find(1)) {
cout << -1 << '\n';
return 0;
}
}
for(auto [l ,r] : ans) {
cout << l << ' ' << r << '\n';
}
}