单 log 并查集线段树分治
省流:标题。https://www.luogu.com.cn/problem/P5787 的单 log 做法,打不过双 log。
本质上我们要干这么一个事情:对于一条边,它会被拆到 log 个线段树节点上。我们希望这些节点上这条边合并的总复杂度是 findf 总复杂度是一个 log 即可。
我们首先考虑魔改掉我们的并查集。当我们抵达一个线段树节点时,对其上的边进行 dfs/bfs,并把一个联通块的所有点连到这个联通块的起点(抛弃启发式合并)。我们发现同一个节点上所有边只会让一个点的深度至多增加一。
然后我们考虑在抵达一个线段树节点时,直接将其子树内所有涉及到的边执行 findf。由于我们的合并结构,一条边被执行 findf 的复杂度实际上就只有常数时间(不超过一)。于是我们就把这部分复杂度直接摊成了线段树区间加边的复杂度。于是这个东西就对了。
代码其实很好写。但是常数特别大,要卡评测机波动,也可能是实现问题。有一些看起来意义不明的数组和写法都是为了卡常。
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 2e5 + 9, M = 1 << 18;
int n, m, k, fa[N], q[N], tog[N << 1];
vector<tuple<int, int, int, int, int, int>> sgt[M];
vector<int> es[N];
bool vst[N];
template <typename T>
void clear(vector<T>& v) {
vector<T>().swap(v);
}
inline int findf(int x) { return fa[x] ?: x; }
void dfs(int w, int L, int R, int p) {
int s = p, t = p, z = 0;
for (auto [l, r, u, x, v, y] : sgt[w])
if (r >= R && l <= L) {
es[u].push_back(y), es[y].push_back(u), tog[z++] = u;
es[v].push_back(x), es[x].push_back(v), tog[z++] = v;
}
for (int u : span(tog, z))
if (!exchange(vst[u], true))
for (q[t++] = u; s < t; ++s)
for (int y : es[q[s]])
if (!exchange(vst[y], true)) fa[q[t++] = y] = u;
for (int x : span(q + p, t - p)) clear(es[x]), vst[x] = false;
bool tg = true;
for (auto [l, r, u, x, v, y] : sgt[w])
if (r >= R && l <= L) tg &= findf(u) != findf(v);
if (!tg)
for (int i = L; i <= R; ++i) cout << "No" << '\n';
else if (L == R)
cout << "Yes" << '\n';
else {
int mid = (L + R) >> 1;
for (auto [l, r, u, x, v, y] : sgt[w]) {
if (r >= R && l <= L) continue;
u = findf(u), v = findf(v), x = findf(x), y = findf(y);
if (r > mid) sgt[w << 1 | 1].emplace_back(l, r, u, x, v, y);
if (l <= mid) sgt[w << 1].emplace_back(l, r, u, x, v, y);
}
clear(sgt[w]), dfs(w << 1, L, mid, t), dfs(w << 1 | 1, mid + 1, R, t);
}
for (int x : span(q + p, t - p)) fa[x] = 0;
}
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
cin >> n >> m >> k;
for (int x, y, l, r; m; --m) {
if (cin >> x >> y >> l >> r, ++l > r) continue;
sgt[1].emplace_back(l, r, x, x + n, y, y + n);
}
return dfs(1, 1, k, 0), cout << flush, 0;
}
以上。