题目描述
难度分:2000
输入T(≤104)表示T组数据。所有数据的n之和≤2×105,m之和≤2×105,q之和≤2×105。
每组数据输入n(2≤n≤2×105),m(1≤m≤2×105),表示一个n 点m边的无向图。节点编号从1开始。保证图中无自环和重边。
然后输入长为n的数组h(1≤h[i]≤109)。每个节点有一座山,第i座山的高度为h[i]。
然后输入m条边。
然后输入q(1≤q≤2×105)和q个询问,每个询问输入a、b、e(0≤e≤109)。
从第i座山移动到和i相邻的第j座山,你的能量会减少h[j]−h[i]。如果这个值是负数则你会增加能量。
只有在移动后能量≥0的情况下才能从i移动到j。对于每个询问,回答:在你初始能量为e 的情况下,能否从第a座山移动到第b座山?输出YES
或NO
。注意节点编号从1开始。
输入样例1
2
7 7
1 5 3 4 2 4 1
1 4
4 3
3 6
3 2
2 5
5 6
5 7
5
1 1 3
6 2 0
4 7 0
1 7 4
1 7 2
6 5
4 7 6 2 5 1
1 3
5 3
1 5
2 4
6 2
5
1 5 1
1 3 1
1 2 1000
6 2 6
6 2 5
输出样例1
YES
NO
YES
YES
NO
YES
NO
NO
YES
NO
输入样例2
2
3 2
1 3 9
1 2
2 3
5
1 1 1
3 2 2
1 1 2
3 3 0
1 2 1
3 3
1 4 1
1 2
2 3
1 3
5
3 3 9
1 3 6
1 1 2
3 3 6
3 3 4
输出样例2
YES
YES
YES
YES
NO
YES
YES
YES
YES
YES
输入样例3
1
6 10
7 9 2 10 8 6
4 2
6 1
4 5
3 5
6 4
1 3
2 6
6 5
1 2
3 6
5
4 4 8
3 3 1
5 5 9
2 1 7
6 6 10
输出样例3
YES
YES
YES
YES
YES
算法
离线+并查集
这个题直接瞪眼还是没什么思路的,但是稍微动动笔就豁然开朗了。假设初始能量为e,现在要从0到k,途中会依次经过0,1,2,…,k−1,k这些节点,则必须满足:
e−(h1−h0)≥0
e−(h1−h0)−(h2−h1)≥0
e−(h1−h0)−(h2−h1)−(h3−h2)≥0
……
e−(h1−h0)−(h2−h1)−(h3−h2)…−(hk−hk−1)≥0
相互抵消后发现只需要e+h0≥hi(i∈[1,k])就可以了。因此我们可以先将所有边(u,v)按照max(h[u],h[v])排序,所有询问按照e+h[a]排序,按照这个顺序考虑每个询问,对于某个询问i的阈值threshold=ei+h[ai],把边集中max(h[u],h[v])不超过threshold的节点u、v都加入到图中,用并查集维护连通性(合并u和v)。然后遍历属于这个阈值的所有询问,看看ai和bi是不是连通的。
遍历更大的threshold时,其实小阈值的边集已经全部被加入到图中了,因此指向边集的指针并不会回退。这样一来跑一遍双指针算法就能求得所有答案,最后再把答案按照询问顺序输出就可以了。
复杂度分析
时间复杂度
对边集和询问排序的时间复杂度为O(mlog2n)和O(qlog2q),然后双指针算法求答案的时间复杂度为O((m+q)log2n),其中log2n是对并查集执行合并操作时的大约时间复杂度。因此,整个算法的时间复杂度为O(mlog2m+qlog2q+(m+q)log2n)。
空间复杂度
除了输入的h数组,并查集的空间复杂度为O(n),边数组的空间复杂度为O(m),答案数组ans的空间复杂度为O(q)。因此,整个算法的额外空间复杂度为O(n+m+q)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int T, n, m, q, h[N], p[N];
bool ans[N];
int find(int x) {
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int 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);
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
scanf("%d", &h[i]);
p[i] = i;
}
vector<array<int, 3>> edges;
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
edges.push_back({max(h[u], h[v]), u, v});
}
sort(edges.begin(), edges.end());
scanf("%d", &q);
map<int, vector<array<int, 3>>> queries;
for(int i = 1; i <= q; i++) {
ans[i] = false;
int a, b, e;
scanf("%d%d%d", &a, &b, &e);
queries[h[a] + e].push_back({a, b, i});
}
int j = 0;
for(auto&[threshold, qs]: queries) {
while(j < m && edges[j][0] <= threshold) {
int u = edges[j][1], v = edges[j][2];
merge(u, v);
j++;
}
for(auto&query: qs) {
int a = query[0], b = query[1], index = query[2];
ans[index] = is_connected(a, b);
}
}
for(int i = 1; i <= q; i++) {
if(ans[i]) {
puts("YES");
}else {
puts("NO");
}
}
puts("");
}
return 0;
}