CF319E Ping-Pong
题意
如果两个线段相交(不包括端点),那么你可以从一个线段移动到另外一个线段。动态添加线段,询问能否从一个线段前往另外一个线段。
思路
我们不难想到利用 \(scc\) 来解决点对之间的关系(经典例题《炸弹》),离散化后使用权值线段树保存点对关系。
具体来说,用有向边表示两个线段能相互影响,如果有个线段 \(x\) 和另外一个线段 \(y\) 相交(不包括端点),那么在 \(x\) 和 \(y\) 之间连接双向边,它们一定在一个 \(scc\) 中;如果一个线段完全包含在一个线段之中,那么只有大线段能影响小线段,所以只从大线段连边到小线段。
这样我们跑完有向图缩点,一个 \(scc\) 之中所有线段可以互相到达,而不同 \(scc\) 之间的点只能走一些单向边相互联系。
而这个题目非常仁慈,“保证加入的区间长度严格单调递增”,所以如果后面的一个线段的一个端点在前面的一个区间之内,那它们一定相交而不被包含,它们一定在一个 \(scc\) 之内。
因此我们不用真正把图建出来再跑强连通分量,用并查集维护强连通分量。当有新线段来时,将这条线段两个端点与所有线段进行比较,如果相交,则将起并入一个 \(scc\),为了高效比较,将所有线段放入线段树进行处理。
对于询问,如果两条线段在一个 \(scc\) 中,那么一定是 YES
,如果两线段对应的 \(scc\) 有包含关系,那么也是 YES
,不可能出现其他情况,否则就被并入一个 \(scc\) 了。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010; // Be Happy
struct node { // Segment Tree
vector<int> ver;
} tr[N << 2];
struct query { // record all queries
int opt, a, b;
} q[N];
struct union_find { // union find
int fa[N];
int l[N], r[N]; // record the 'l' and 'r' of all the segments in a scc
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return;
l[y] = min(l[y], l[x]);
r[y] = max(r[y], r[x]);
fa[x] = y;
}
union_find() { iota(fa, fa + N, 0); }// init
} f;
int l[N], r[N]; // record 'l' and 'r'
void add(int u, int l, int r, int x, int seg) { // merge a new segment and the old segments to a scc
if (tr[u].ver.size()) { // merge
while (tr[u].ver.size()) {
f.merge(tr[u].ver.back(), seg);
tr[u].ver.pop_back();
}
tr[u].ver.push_back(seg);
}
if (l == r) return;
int mid = l + r >> 1;
if (x <= mid) add(u << 1, l, mid, x, seg);
else add(u << 1 | 1, mid + 1, r, x, seg);
}
void addtag(int u, int l, int r, int pl, int pr, int seg) { // add a new segment into the Segment Tree
if (pl <= l && r <= pr) {
tr[u].ver.push_back(seg);
return;
}
int mid = l + r >> 1;
if (pl <= mid) addtag(u << 1, l, mid, pl, pr, seg);
if (pr > mid) addtag(u << 1 | 1, mid + 1, r, pl, pr, seg);
}
int n;
void solve() {
cin >> n;
vector<int> data;
for (int i = 1; i <= n; i++) {
cin >> q[i].opt >> q[i].a >> q[i].b;
if (q[i].opt == 1) {
data.emplace_back(q[i].a);
data.emplace_back(q[i].b);
}
}
sort(data.begin(), data.end());
data.erase(unique(data.begin(), data.end()), data.end());
for (int i = 1; i <= n; i++) { // Discretization [刚学的](离散化)
if (q[i].opt == 1) {
q[i].a = lower_bound(data.begin(), data.end(), q[i].a) - data.begin() + 1;
q[i].b = lower_bound(data.begin(), data.end(), q[i].b) - data.begin() + 1;
}
}
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (q[i].opt == 1) {
cnt++;
l[cnt] = f.l[cnt] = q[i].a, r[cnt] = f.r[cnt] = q[i].b;
add(1, 1, data.size(), q[i].a, cnt);
add(1, 1, data.size(), q[i].b, cnt);
if (q[i].a + 1 <= q[i].b - 1) addtag(1, 1, data.size(), q[i].a + 1, q[i].b - 1, cnt);
}
else {
int fa = f.find(q[i].a);
int fb = f.find(q[i].b);
if (fa == fb || (f.l[fb] <= f.l[fa] && f.r[fa] <= f.r[fb] && (l[fa] != f.l[fb] || r[fa] != f.r[fb]))) cout << "YES\n";// if the segment is in other segments or it is in a scc, output "YES"
else cout << "NO\n";
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}