BZOJ 3226 [SDOI2008]校门外的区间

本文介绍了一道关于集合操作的编程题目,通过使用线段树实现高效的区间更新与查询,解决了包含并集、交集等多种操作的问题。文章详细解释了如何通过坐标变换将开闭区间统一处理的方法。

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3226

题意:
给定一个空集合S,维护五种集合与集合的操作,将最终得到的集合输出。
对于集合S和T,操作包括 S=ST S=ST S=ST S=TS S=ST
每次的操作是给定一个T,并保证T表示的元素是一段连续的区间,可能开可能闭,区间端点均为整数。
最终输出的集合S要按升序输出每个连续的区间。
操作数 70000 ,集合的数字 [0,65535]

题解:
考虑到区间的端点均为整数,若不考虑开闭问题则可以转化为整数点被覆盖的问题,而考虑开区间的话也只需要在任意两个整点之间加入一个点即可,不妨认为是 X.5 。再将坐标全部乘2则转化为整数点闭区间的问题。
考虑到集合内整点的个数并不多,坐标变换之后也只有131071个整点,不妨用一个01序列表示一个集合,序列第i位是1则表示集合中有整点i

现在考虑五种操作:(设坐标变换后T的区间为 [l,r] ,坐标最大值为 n
- S=ST,相当于把S的 [l,r] 位置赋值为1。
- S=ST ,相当于把S的 [0,l) (r,n] 位置赋值为0。
- S=ST ,相当于把S的 [l,r] 位置赋值为0。
- S=TS ,相当于把S的 [0,l) (r,n] 位置赋值为0,S的 [l,r] 位置的01互换。
- S=ST ,相当于把S的 [l,r] 位置的01互换。

考虑用线段树来维护这五个操作,需要对序列的一段染色,对序列的一段反色,输出每个连续的1区间(坐标需要变换回来)。
前两个操作需要 O(logn) 的复杂度,第三个操作只会做一次,复杂度 O(n) 即可。
所以线段树维护区间的颜色(0或1),打上反色的标记,注意两种标记下传的先后顺序即可,最终的操作只需要合并一下区间。
(听说线段树的做法比分块慢很多)

代码:

#include <cstdio>
#include <cstdlib>
const int n = 1 << 17;
int tot = 0, out[n][2];
struct SegmentTree
{
    SegmentTree *ch[2];
    int l, r, m;
    bool flag, Xor, color;
    SegmentTree(int l, int r) : l(l), r(r), m(l + r >> 1) { ch[0] = ch[1] = NULL; flag = 1; Xor = color = 0; }
    void update()
    {
        if(l == r) return;
        if(flag)
        {
            flag = 0;
            ch[0] -> flag = ch[1] -> flag = 1;
            ch[0] -> Xor = ch[1] -> Xor = 0;
            ch[0] -> color = ch[1] -> color = color;
        }
        if(Xor)
        {
            Xor = 0;
            ch[0] -> Xor ^= 1;
            ch[1] -> Xor ^= 1;
        }
    }
    void fill(const int s, const int t, const int col)
    {
        if(s <= l && r <= t)
        {
            flag = 1;
            Xor = 0;
            color = col;
            return;
        }
        update();
        if(s <= m) ch[0] -> fill(s, t, col);
        if(t > m) ch[1] -> fill(s, t, col);
    }
    void rev(const int s, const int t)
    {
        if(s <= l && r <= t)
        {
            Xor ^= 1;
            return;
        }
        update();
        if(s <= m) ch[0] -> rev(s, t);
        if(t > m) ch[1] -> rev(s, t);
    }
    void make_tree()
    {
        if(l == r) return;
        (ch[0] = new SegmentTree(l, m)) -> make_tree();
        (ch[1] = new SegmentTree(m + 1, r)) -> make_tree();
    }
    void pre()
    {
        if(flag)
        {
            color ^= Xor;
            if(color) { out[tot][0] = l; out[tot++][1] = r; }
            return;
        }
        update();
        ch[0] -> pre();
        ch[1] -> pre();
    }
    void del()
    {
        if(l == r)
            return;
        ch[0] -> del();
        delete ch[0];
        ch[1] -> del();
        delete ch[1];
    }
} *root;

int main()
{
    char type, flag_l, flag_r;
    int l, r;
    (root = new SegmentTree(0, n)) -> make_tree();
    while(scanf("%c %c%d,%d%c\n", &type, &flag_l, &l, &r, &flag_r) != EOF)
    {
        l <<= 1;
        r <<= 1;
        if(flag_l == '(') ++l;
        if(flag_r == ')') --r;
        switch(type)
        {
            case 'U': root -> fill(l, r, 1); break;
            case 'I': if(l) root -> fill(0, l - 1, 0); if(r < n) root -> fill(r + 1, n, 0); break;
            case 'D': root -> fill(l, r, 0); break;
            case 'C': if(l) root -> fill(0, l - 1, 0); if(r < n) root -> fill(r + 1, n, 0); root -> rev(l, r); break;
            case 'S': root -> rev(l, r); break;
        }
    }
    root -> pre();
    if(!tot) printf("empty set\n");
    else
    {
        int i;
        for(i = 0; i < tot - 1; ++i)
            if(out[i][1] + 1 == out[i + 1][0]) out[i + 1][0] = out[i][0];
            else
            {
                if(out[i][0] & 1) putchar('(');
                else putchar('[');
                printf("%d,%d", out[i][0] >> 1, out[i][1] + 1 >> 1);
                if(out[i][1] & 1) putchar(')');
                else putchar(']');
                putchar(' ');
            }
        if(out[i][0] & 1) putchar('(');
        else putchar('[');
        printf("%d,%d", out[i][0] >> 1, out[i][1] + 1 >> 1);
        if(out[i][1] & 1) putchar(')');
        else putchar(']');
        putchar(' ');
        putchar('\n');
    }
    root -> del();
    delete root;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值