推荐

如何解决两个区间的并查集

__liujy 2026-5-12 17:25:50 15 浏览 3 点赞 0 收藏

例题

首先考虑暴力怎么写,对于每一次 22 操作,直接暴力加边,时间复杂度为 O(nqα(n))O(nq\alpha{(n)})

瓶颈在于 22 操作,考虑如何优化。

对于一次 22 操作,可以看成若干次 22 操作的结合体,具体可以看下面两张图:

红色的是原始序列,蓝色和紫色分别是可以被分割的区域,而灰色不行,因为红色序列中有一段区间没有被包含到。

分成两个区间比较方便,接下来考虑如何分成两个区间。

那分完这两个区间之后考虑这两个区间有什么性质比较好维护。区间长度是 22 的整数次方是一个比较好的性质,可以很自然的想到 ST 表。

在下文中 [l,r,s][l, r, s] 表示为满足 2s=rl+12^s = r - l + 1 的区间 [l,r][l, r]

接下来区间长度已经是 22 的整数次幂了,就可以对于每一次要进行操作的区间 [l,r,s][l, r, s],就可以每次分成 [l,r,s1][l, r, s - 1][l+2s1,r+2s1,s1][l + 2^{s - 1}, r + 2^{s - 1}, s - 1] 进行操作。

这么进行下来,操作 22 的时间复杂度就成功降到了 O(nlognα(n))O(n\log{n}\alpha{(n)}) 了。

代码:

#include<bits/stdc++.h>
const int MAXN = 2e5 + 5, MAXM = 30;
int n, q;
int lg[MAXN], fa[MAXN][MAXM], mi[MAXN], mx[MAXN];
inline void init()
{
    lg[0] = -1;
    for(int i = 1; i <= n; i++)
        lg[i] = lg[i >> 1] + 1;
    for(int i = 1; i <= n; i++)
        fa[i][0] = i;
    for(int j = 1; j <= lg[n]; j++)
        for(int i = 1; i <= n; i++)
            fa[i][j] = i;
    for(int i = 1; i <= n; i++)
        mi[i] = mx[i] = i;
}
inline int getf(int x, int s)
{
    if(fa[x][s] == x) return x;
    return fa[x][s] = getf(fa[x][s], s);
}
inline void modify(int l, int r, int s)
{
    int xf = getf(l, s), yf = getf(r, s);
    if(xf == yf) return;
    fa[xf][s] = yf;
    if(!s)
    {
        mi[yf] = std::min(mi[yf], mi[xf]);
        mx[yf] = std::max(mx[yf], mx[xf]);
        return;
    }
    modify(l, r, s - 1), modify(l + (1 << (s - 1)), r + (1 << (s - 1)), s - 1);
}
int main()
{
    scanf("%d%d", &n, &q);
    init();
    for(int _ = 1, op, x, l, r, len; _ <= q; _++)
    {
        scanf("%d", &op);
        if(op == 1) scanf("%d", &x);
        else scanf("%d%d%d", &l, &r, &len);
        if(op == 1)
            printf("%d %d\n", mi[getf(x, 0)], mx[getf(x, 0)]);
        else
        {
            int s = lg[len];
            modify(l, r, s);
            modify(l + len - (1 << s), r + len - (1 << s), s);
        }
    } return 0;
}

所以当两个区间进行操作时,可以考虑用像 ST 表这类的东西优化复杂度。

评论

1 条
YIZHIYANG初来乍到
2026-5-12 23:56:16

前排