Problem Description
给出 n n n 座山,一些山之间有公路相连,第 i i i 座山高为 h i h_i hi 。
如果 i i i 山和 j j j 山之间有条路,那么 V l a d Vlad Vlad 可以花费 h j − h i h_j-h_i hj−hi 的能量从 i i i 山转移到 j j j 山,如果转移过程中能量降到 0 0 0 以下,则无法转移。注意 h j − h i h_j-h_i hj−hi 可以是负值, 对应为能量回复。
现在请你回答如果 V l a d Vlad Vlad 在能量初始为 e e e 的情况下,是否有可能从 a a a 山走到 b b b 山。
Input
第一行输入一个整数 t t t ,表示测试组数。
对于每组测试,
第一行输入 n , m ( 2 ≤ n ≤ 2 × 1 0 5 , 1 ≤ m ≤ m i n ( n ⋅ ( n − 1 ) 2 , 2 × 1 0 5 ) ) n,m(2 \le n \le 2 \times 10^5,1 \le m \le min(\frac{n·(n-1)}{2},2 \times 10^5)) n,m(2≤n≤2×105,1≤m≤min(2n⋅(n−1),2×105)) ,表示山和路的个数。
第二行输入 n n n 整数 h 1 , h 2 , h 3 , … , h n ( 1 ≤ h i ≤ 1 0 9 ) h_1,h_2,h_3,…,h_n(1 \le h_i \le 10^9) h1,h2,h3,…,hn(1≤hi≤109) ,表示每座山的高度。
接下来 m m m 行输入两个整数 u , v ( 1 ≤ u , v ≤ n , u ≠ v ) u,v(1 \le u,v \le n,u \not= v) u,v(1≤u,v≤n,u=v) ,表示 u u u 山和 v v v 山之间存在一条道路,不存在道路出现两次。
接下来一行输入一个整数 q ( 1 ≤ q ≤ 2 × 1 0 5 ) q(1 \le q \le 2 \times 10^5) q(1≤q≤2×105) ,表示查询次数。
接下来 q q q 行输入三个整数 a , b , e ( 1 ≤ a , b ≤ n , 0 ≤ e ≤ 1 0 9 ) a,b,e(1 \le a,b \le n,0 \le e \le 10^9) a,b,e(1≤a,b≤n,0≤e≤109) ,表示起点,终点和初始能量。
Output
对于每次询问,输出 “ Y e s Yes Yes” 或 “ N o No No” ,表示是否可达。
Solution
首先 a a a 到 b b b 如果连通,那么经过的山其实只有所有路径最高的山中的最小高度才是关键限制,只要 h m a x − h a ≤ e h_{max}-h_a \le e hmax−ha≤e 就能可达,也就是说需要 h m a x ≤ h a + e h_{max} \le h_a+e hmax≤ha+e 满足,那么这个表意也等同于初始山高为 h a + e h_a+e ha+e ,是否能找到一条路径,使路径上的所有山高都低于 h a + e h_a+e ha+e ,现在我们便需考虑如何快速求出 h m a x h_{max} hmax 。
考虑到 h m a x = m a x ( h a , h c , h d … , h e , h b ) = m a x ( m a x ( h a , h c ) , m a x ( h c , h d ) , … , m a x ( h e , h b ) ) h_{max}=max(h_a,h_c,h_d…,h_e,h_b)=max(max(h_a,h_c),max(h_c,h_d),…,max(h_e,h_b)) hmax=max(ha,hc,hd…,he,hb)=max(max(ha,hc),max(hc,hd),…,max(he,hb)),我们可以将从 i i i 到 j j j 的边权设为 m a x ( h i , h j ) max(h_i,h_j) max(hi,hj) ,那么就转化成了求 a a a 到 b b b 所有路径最大边权的最小值。
又由于
原图中两点之间的所有简单路径上最大边权的最小值 = = = 最小生成树两点之间简单路径上的最大值。
我们可以去构建原图的最小生成树,然后求 a a a 到 b b b 上的最大边权,对于这步查询,我们可以采用 L C A LCA LCA 等方式去以 O ( q l o g n ) O(qlogn) O(qlogn) 的时间复杂度便能通过此题,此处介绍另一种离线做法。
考虑到 K r u s k a l Kruskal Kruskal 最小生成树的做法,每次将剩余边中的最小边添加进图中,能否使两个不连通的块连通。
而每次添加了这样的最小边之后,图中的所有边权 ≤ \le ≤ 最小边,此时我们发现,只要将 w i ≤ h a + e w_i \le h_a+e wi≤ha+e 的所有边加进来后,判断 a a a 和 b b b 是否连通即可。
那么我们现在的做法就变成了先将所有查询存下来,然后以 h a + e h_a+e ha+e 为关键值排序,然后从小到大枚举每个查询,将 w i ≤ h a + e w_i \le h_a+e wi≤ha+e 的边添加到图中, a , b a,b a,b 是否连通就变成了在并查集中 a , b a,b a,b 是否在同一连通块,最终复杂度为 O ( m l o g m + q l o g q ) O(mlogm+qlogq) O(mlogm+qlogq) 。
Code
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
struct DSU {
vector<int>p;
DSU(int n): p(n) {iota(p.begin(), p.end(), 0);}
int leader(int x) {
while (x != p[x])x = p[x] = p[p[x]];
return x;
}
bool merge(int a, int b) {
a = leader(a), b = leader(b);
if (a == b)return false;
p[b] = a;
return true;
}
bool same(int x, int y) {return leader(x) == leader(y);}
};
void solve() {
int n, m;
cin >> n >> m;
vector<int>h(n + 1);
for (int i = 1; i <= n; i++)cin >> h[i];
vector<array<int, 3>>edge(m);
for (int i = 1; i <= m; i++) {
int a, b;
cin >> a >> b;
edge[i - 1] = {max(h[a], h[b]), a, b};
}
sort(edge.begin(), edge.end());
int q;
cin >> q;
vector<int>ans(q);
vector<array<int, 4>>query(q);
for (int i = 0; i < q; i++) {
int a, b, e;
cin >> a >> b >> e;
query[i] = {h[a] + e, a, b, i};
}
int j = 0;
DSU dsu(n + 1);
sort(query.begin(), query.end());
for (auto [w, u, v, i] : query) {
while (j < m && edge[j][0] <= w) {
dsu.merge(edge[j][1], edge[j][2]);
j++;
}
ans[i] = dsu.same(u, v);
}
for (int i = 0; i < q; i++) {
if (ans[i])cout << "Yes" << endl;
else cout << "No" << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)solve();
}