昨天学习了 Boruvka 算法求最小生成树。
它有什么用呢?
有时候给出的图的边数可能达到 级别,这时无法使用传统的 Prim 或 Kruskal 求最小生成树,这时就要用到 Boruvka 算法。
Boruvka 算法的流程如下:
最开始,共有 n 个联通块,分别是每个结点。
每次对于每个联通块,寻找联通块内的点与联通块外的点最小的边,记录下来,找完所有最小的边后统一合并。不断进行这样的操作,直到联通块个数为 1。
为什么这样做是正确的呢?它其实是每次进行多步的 Kruskal,或者说多路增广的 Kruskal,我们发现这两者的操作本质上是相同的,都是每次找出最小的边,只不过 Boruvka 将其中一些边的合并提前进行了,因为对于一个联通块来说,总有一条边要让这个联通块和另外一个联通块合并,根据 Kruskal 算法,与其他联通块内的点相连最小的边一定是要算进去的,因此将其提前合并依旧是正确的。
为什么时间复杂度是正确的?因为每次每个联通块都会与另外一个联通块合并,所以每次联通块个数都至少缩减为原来的 ,因此只会有 次合并,若每次合并总时间复杂度是接近线性的,那么总时间复杂度就是 的。
这里有道例题。
题意:
给出长度为 的序列 ,定义两点 间边权为 ,开始时在点 ,边权在 中的两点可以互相到达, 是常数,每次给出 和 ,询问对于 ,能否从点 到达点 。
稍微把边权转化一下,发现 的点联通,那么令新的边权为 ,对于一条路径,其最大边权不大于 。于是,若是 到 所有路径中最小的最大边权不大于 ,就可以从 到达 。
最小的最大边权,显然求这个东西需要最小瓶颈生成树,或者 Kruskal 重构树。实际上前者在本题是可以实现的,因为 是固定的,询问离线下来不断从 往外扩展就好了,估计还很好写。那还学 Boruvka 干什么
考虑使用 Boruvka 算法,对于每次求一个联通块内的点与其他点最小边权操作,可以将联通块内的点从存着所有点的 set 中删掉,然后对于联通块内每个点 ,分别二分出与之相连边权最小的点,也就是 与 最接近的点 ,这样的点可能有四个(左边两个右边两个),取最小的那个即可。合并时用启发式合并来合并联通块的点,这样做扫完每个联通块的时间复杂度是 的,另外启发式合并的总时间复杂度是 的,因此总时间复杂度是 。
最后,由于 是常量,所以直接以 为根节点 dfs 一遍即可求出边权最大值,并不需要树剖。
Code:
#include <bits/stdc++.h>
#define ll long long
#define lp(i, j, n) for(int i = j; i <= n; ++i)
#define dlp(i, n, j) for(int i = n; i >= j; --i)
#define mst(n, v) memset(n, v, sizeof(n))
#define mcy(n, v) memcpy(n, v, sizeof(v))
#define INF 1e18
#define MAX4 0x3f3f3f3f
#define MAX8 0x3f3f3f3f3f3f3f3f
#define mkp(a, b) make_pair(a, b)
#define pii pair<int, int>
#define pll pair<ll, ll>
#define co(x) cerr << (x) << ' '
#define cod(x) cerr << (x) << endl
#define fi first
#define se second
#define eps 1e-8
#define pb(x) emplace_back(x)
using namespace std;
const int N = 200010, M = 1000010;
int n, Q, s, D, ans[N], fa[N];
int a[N], no[M], dis[N];
pii to[N];
vector<int> pt[N], e[N];
set<int> pt0, rt;
int mab(int x) { return x < 0 ? -x : x; }
int getv(int i, int j) { return mab(mab(a[i] - a[j]) - D); }
vector<int> cl;
void mer(int x, int y) {
// co(x), cod(y);
e[x].pb(y), e[y].pb(x);
int fx = fa[x], fy = fa[y];
if(pt[fx].size() > pt[fy].size()) swap(x, y), swap(fx, fy);
cl.emplace_back(fx);
for(auto p : pt[fx]) pt[fy].emplace_back(p), fa[p] = fy;
}
void solve() {
lp(i, 1, n) rt.insert(i), fa[i] = i, pt[i].pb(i), pt0.insert(a[i]);
while(rt.size() > 1) {
cod(rt.size());
for(auto r : rt) {
for(auto p : pt[r]) pt0.erase(a[p]);
int res = 1e9;
for(auto p : pt[r]) {
auto upd = [&] (int idx) { if(getv(idx, p) < res) res = getv(idx, p), to[r] = { p, idx }; };
auto it = pt0.lower_bound(a[p] + D);
if(it != pt0.end()) upd(no[*it]);
if(it != pt0.begin()) --it, upd(no[*it]);
it = pt0.lower_bound(a[p] - D);
if(it != pt0.end()) upd(no[*it]);
if(it != pt0.begin()) --it, upd(no[*it]);
}
for(auto p : pt[r]) pt0.insert(a[p]);
}
for(auto r : rt) {
if(fa[to[r].fi] != fa[to[r].se]) mer(to[r].fi, to[r].se);
}
for(auto p : cl) rt.erase(p);
cl.clear();
}
}
void dfs(int now, int fa) {
for(auto v : e[now]) {
if(v == fa) continue;
dis[v] = max(dis[now], getv(now, v));
dfs(v, now);
}
}
signed main() {
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
#ifndef READ
ios::sync_with_stdio(false);
cin.tie(0);
#endif
cin >> n >> Q >> s >> D;
lp(i, 1, n) cin >> a[i], no[a[i]] = i;
int k, idx;
solve();
dfs(s, 0);
lp(i, 1, Q) {
cin >> idx >> k;
if(dis[idx] <= k) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}