题目描述
难度分:2600
输入n(2≤n≤3×105),m(n−1≤m≤3×105),x(1≤x≤109),表示一个n点m边的无向图(节点编号从1开始)。
输入长为n的数组a(0≤a[i]≤109),表示每个点的点权。
然后输入m条边。保证图中没有自环和重边。保证图是连通的。
把点视作城市,把边视作道路。一场地震摧毁了所有的道路,现在需要修复其中n−1 条道路,将所有城市连接起来。
城市i有a[i]吨沥青。如果城市i和城市j的沥青总量至少是x,那么可以修复i和j之间的道路,并消耗x吨沥青。
一个城市的沥青可以沿着修好的道路运往其它城市。是否可以将所有城市连接起来?
如果不能,输出NO
。如果能,输出YES
,并按照修路的顺序,依次输出这n−1 条边的编号(输入的第i条边的编号是i,从1开始)。如果有多种方案,输出任意一种。
输入样例1
5 4 1
0 0 0 4 0
1 2
2 3
3 4
4 5
输出样例1
YES
3
2
1
4
输入样例2
2 1 2
1 1
1 2
输出样例2
YES
1
输入样例3
2 1 2
0 1
1 2
输出样例3
NO
输入样例4
5 6 5
0 9 4 0 10
1 2
1 3
2 3
3 4
1 4
4 5
输出样例4
YES
6
4
1
2
算法
Kruskal
算法+DFS
先看看什么时候是无解的,可以发现,当Σni=1a[i]<x×(n−1)时,所有城市的沥青加起来都不够连接n−1条道路,此时肯定无解。
证明一下其他情况下肯定有解,对于一个叶子节点u:
- 如果a[u]≥x,那直接把u→fa这条边修复(fa为u的父节点),将u和fa缩成一个点,此时这个点有沥青a[x]+a[fa]−x吨。
- 如果a[u]<x,把u“删除”,此时还剩下n−1个节点,由于a[u]<x,那剩下的节点就满足沥青总量≥x×(n−2)。按照归纳假设,剩下的节点就可以连成一棵树,那么剩余的沥青就都能为修复u→这条边所用(可以运过来),而沥青的总量Σni=1a[i]≥x×(n−1),因此这条边也肯定能修复。因此一旦a[u]<x,那u→fa这条边就最后再修复。
利用Kruskal
算法选出一棵生成树,然后考虑如何将这棵树上的边都修复。不妨以1为根节点进行DFS
,申请一个长度为n−1的答案数组ans(反正有解的话答案就是n−1条边):
- 如果遍历完以u为根的子树后,当前节点u满足a[u]≥x,将u→fa这条边直接顺序加到答案数组ans中。
- 否则就将u→fa这条边倒着插入到答案数组ans中,表示这条边最后才选。越远离根节点、满足a[u]<x的节点u,其对应的边u→fa就越后选。
注意在这个DFS
的过程中,一边修复道路,需要动态更新a[u]的值。DFS
完成后顺序输出ans数组中边的编号即可,实现有一些细节,详见代码。
复杂度分析
时间复杂度
Kruskal
选出生成树的时间复杂度为O(mlogm),其中O(logm)是合并节点时的操作,O(m)是遍历边集的时间复杂度。接下来对整个图进行DFS
,需要遍历到所有选到的n−1条边和n个节点,且不会重复遍历,因此时间复杂度是O(n)。综上,整个算法的时间复杂度是O(mlogm+n)。
空间复杂度
除去输入的点权数组a和输出的答案数组ans,并查集的父节点数组p的空间复杂度为O(n),生成树的邻接表空间消耗是节点数+边数,为n+n−1,仍然是O(n)级别的。综上,整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <array>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 300010;
int n, m, x, p[N], ans[N], cnt1, cnt2;
LL tot, a[N];
vector<array<int, 2>> graph[N];
int find(int x) {
return p[x] == x? x: p[x] = find(p[x]);
}
void merge(int x, int y) {
int rx = find(x), ry = find(y);
if(rx != ry) {
p[rx] = ry;
}
}
bool is_connected(int x, int y) {
return find(x) == find(y);
}
// fa通过ei这条边到u
void dfs(int u, int fa, int ei) {
for(auto&tup: graph[u]) {
int v = tup[0];
if(v == fa) continue;
dfs(v, u, tup[1]);
}
if(u == 1) return;
if(a[u] >= x) {
// 连接u和fa
a[fa] += a[u] - x;
ans[++cnt1] = ei;
}else {
ans[--cnt2] = ei;
}
}
int main() {
scanf("%d%d%d", &n, &m, &x);
cnt1 = 0;
cnt2 = n;
tot = 0;
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
p[i] = i;
tot += a[i];
}
vector<array<int, 2>> edges;
for(int i = 0; i < m; i++) {
int x, y;
scanf("%d%d", &x, &y);
edges.push_back({x, y});
if(is_connected(edges[i][0], edges[i][1])) continue;
merge(edges[i][0], edges[i][1]);
graph[edges[i][0]].push_back({edges[i][1], i + 1});
graph[edges[i][1]].push_back({edges[i][0], i + 1});
}
// 无解
if(tot < (n - 1LL)*x) {
puts("NO");
exit(0);
}
// 有解
puts("YES");
dfs(1, 0, 0);
for(int i = 1; i < n; i++) {
printf("%d\n", ans[i]);
}
return 0;
}