题解:CF1814F Communication Towers
对于某些边在一段时间内出现的问题,一般采用线段树分治的方法,线段树分治一般在可撤销并查集的基础上实现。
这题比较巧妙,其实是线段树分治板子加上一个小技巧。
注意到有「
这个时候有一个小技巧:引入标记数组。
我们引入
发现在并查集合并和撤销时可以非常容易地维护
具体地,在
发现如果将
在线段树上
同样的,在
最后若某个点的
时间复杂度为正常的线段树分治复杂度
线段树分治时尤其注意并查集数组的大小。可以像我一样开 2e6,怎么都不会 RE。
顺带一提,本题给的是点的出现时间,故连边时注意左右区间,可以参考代码实现。
#include <bits/stdc++.h>
#define rep(i, j, k) for(int i = j; i <= k; ++i)
#define pb push_back
#define PII pair<int, int>
using namespace std;
const int N = 2e6 + 5;
int n, m, k, u[N], v[N], l[N], r[N], ans[N], tag[N];
vector<PII> e[N * 2];//没必要再乘2了
struct Union {
int fa[N], siz[N];
stack<PII> st;
void init() {
rep(i, 1, n) fa[i] = i, siz[i] = 1;
}
int get(int x) {
return x == fa[x] ? x : get(fa[x]);
}
void merge(int x, int y) {
x = get(x), y = get(y);
if(x == y) return;
if(siz[x] > siz[y]) swap(x, y);
st.push({x, y});
fa[x] = y, siz[y] += siz[x];
tag[x] -= tag[y];
}
void undo(int last) {
while(st.size() > last) {
int x = st.top().first, y = st.top().second;
st.pop();
fa[x] = x, siz[y] -= siz[x];
tag[x] += tag[y];
}
}
} DSU;
stack<PII> T;
struct SegmentTree {
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r >> 1)
void upd(int p, int l, int r, int L, int R, int u, int v) {
if(L > r || l > R) return;
if(L <= l && r <= R)
return e[p].pb({u, v}), void();
upd(ls, l, mid, L, R, u, v), upd(rs, mid + 1, r, L, R, u, v);
}
void ask(int p, int l, int r) {
int S = DSU.st.size();
for(auto ed : e[p]) {
int x = ed.first, y = ed.second;
DSU.merge(x, y);
}
if(l == r) ++tag[DSU.get(1)];
else ask(ls, l, mid), ask(rs, mid + 1, r);
DSU.undo(S);
}
} SGT;
signed main() {
ios::sync_with_stdio(0);
cin.tie(nullptr), cout.tie(nullptr);
int mx = 0;
cin >> n >> m;
DSU.init();
rep(i, 1, n) cin >> l[i] >> r[i], mx = max(mx, r[i]);
rep(i, 1, m) cin >> u[i] >> v[i];
rep(i, 1, m) SGT.upd(1, 0, mx, max(l[u[i]], l[v[i]]), min(r[u[i]], r[v[i]]), u[i], v[i]);
SGT.ask(1, 0, mx);
rep(i, 1, n) if(tag[i]) cout << i << ' ';
}