Loading

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;
}
posted @ 2024-09-10 15:48  SunnyYuan  阅读(3)  评论(0编辑  收藏  举报