CF 319E Ping-Pong 区间的并查集合并

题目大意

定义区间 (a,b) 能走到区间 (c,d) 的条件是 c<a<d c<b<d
现在有两种操作:
Ord=1 新加入一个区间 (a,b) (后加入的区间保证比新加入的区间长)
Ord=2 :询问从第 a 个区间能否走到第b个区间。
现在有 M 个操作,对于第二个操作输出YES NO

M105
a,b109

解题思路

首先我们先考虑两个区间连边的情况,如果不是包含关系,那么这两个区间连的就是双向边,如果是包含关系,那么就是小的区间向大的区间连一条单向边。

我们先讨论双向边的情况,因为是双向边,所以我们能采用并查集维护联通块的思想。如果有两个能互相到达的区间,那么我们就把它们合并成一个区间,具体的实现可以用线段树维护。每次加入一个区间可以在线段树中询问这个区间的左右端点是否在别的区间上。对于线段树的一个节点维护覆盖到这里的区间,当一个端点访问到线段树的一个节点时,就把那些区间合并到当前区间,并把那个节点的的标记赋值为当前区间。其实就是实现了一个区间的并查集的功能。

而对于最后的询问只需看一下两个区间是否在同一个并查集。当然如果是覆盖的情况(单向边)也要考虑进去。

程序

具体实现可以看代码。

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>

using namespace std;

const int MAXN = 2e5 + 5;

struct Query {
    int Ord, x, y;
} Q[MAXN];

vector<int> Tr[MAXN * 4];
map<int,int> Hash;
vector<int> P;
int N, L[MAXN], R[MAXN], Pre[MAXN], Ask[MAXN];

int Get(int Now) {
    if (Pre[Now] == Now) return Now;
    Pre[Now] = Get(Pre[Now]);
    return Pre[Now];
}

void Modify(int Now, int l, int r, int Side, int Ord) {
    if (!Tr[Now].empty()) {
        for (int i = 0; i < Tr[Now].size(); i ++) {
            int Fa = Get(Tr[Now][i]);
            L[Ord] = min(L[Ord], L[Fa]);
            R[Ord] = max(R[Ord], R[Fa]);
            Pre[Fa] = Ord;
        }
        Tr[Now].clear();
        Tr[Now].push_back(Ord);
    }
    if (l == r) return;
    int Mid = (l + r) >> 1;
    if (Side <= Mid) Modify(Now * 2, l, Mid, Side, Ord); else
        Modify(Now * 2 + 1, Mid + 1, r, Side, Ord); 
}

void Push(int Now, int l, int r, int lx, int rx, int Ord) {
    if (lx > rx) return;
    if (r < lx || l > rx) return;
    if (l >= lx && r <= rx) {
        Tr[Now].push_back(Ord);
        return;
    }
    int Mid = (l + r) >> 1;
    Push(Now * 2, l, Mid, lx, rx, Ord), Push(Now * 2 + 1, Mid + 1, r, lx, rx, Ord);
}

bool In(int Side, int l, int r) {
    return Side < r && Side > l;
}

int main() {
    scanf("%d", &N);
    for (int i = 1; i <= N; i ++) {
        scanf("%d%d%d", &Q[i].Ord, &Q[i].x, &Q[i].y);
        if (Q[i].Ord == 1) P.push_back(Q[i].x), P.push_back(Q[i].y);
    }
    sort(P.begin(), P.end());
    unique(P.begin(), P.end());
    int Num = 0, Cnt = 0;
    int pp = 0;
    for (int i = 0; i < P.size(); i ++) Hash[P[i]] = ++ Num;
    for (int i = 1; i <= N; i ++) {
        if (Q[i].Ord == 1) {
            int x = Hash[Q[i].x], y = Hash[Q[i].y];
            L[++ Cnt] = x, R[Cnt] = y;
            Pre[Cnt] = Cnt, Ask[Cnt] = i;
            Modify(1, 1, Num, x, Cnt);
            Modify(1, 1, Num, y, Cnt);
            Push(1, 1, Num, L[Cnt] + 1, R[Cnt] - 1, Cnt);
        } else {
            pp ++;
            int x = Q[i].x, y = Q[i].y;
            int l = Hash[Q[Ask[x]].x], r = Hash[Q[Ask[x]].y];
            int fa = Get(y);
            if (Get(x) == Get(y) || In(l, L[fa], R[fa]) || In(r, L[fa], R[fa])) printf("YES\n");    
                else printf("NO\n");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值