校门三部曲,总算完结了!完结散花!
难度呈阶梯状,都可以用线段树解决。
第一部
P1047 校门外的树(线段树优化)难度⭐⭐
第二部
P1276 校门外的树(增强版)(线段树)校门三部曲难度⭐⭐⭐
第三部
P5568 [SDOI2008]校门外的区间(离散数学应用+线段树+开闭区间处理)难度⭐⭐⭐⭐★

本题题目链接


输出

U [1,5]
D [3,3]
S [2,4]
C (1,5)
I (2,3]

输入

(2,3)

0 a , b 65535 , M 70000 0 \leq a,b \leq 65535, M \leq 70000 0a,b65535,M70000

转化成线段树的区间覆盖+翻转问题

用线段树维护当前区间

并且维护两个标记 c o v , t a g cov,tag cov,tag,分别代表覆盖和翻转的标记

  • U: A B A∪B AB B区间覆盖1
  • I: A B A∩B AB B区间的补集覆盖0
  • D: A B A− B AB B区间覆盖0
  • C: B A B− A BA 全集范围内01翻转,转到II操作
  • S: A B A⊕B AB B区间范围内01翻转

细节处理

输入输出细节
开闭用*2方法处理
覆盖和翻转的优先级
%%%
以上是大佬的思路
上面的各种修改操作自己想一下就懂了,实在不行就画个图看看

我做这道题的感受

  • 学习到了奇怪的 输入输出
  • 刚学的辣鸡离散就派上用场了
  • 注意看清题目要求,少了一个空格De了半个小时的bug 艹
  • 学到了处理开闭区间的方法:*2,判断边界是奇数还是偶数

开闭区间操作:

区间端点均为整数,不妨认为 l r l + 0.5 , r 0.5 (l,r)为(l+0.5,r-0.5) lrl+0.5,r0.5
乘2就可以换算成整数区间

下面的链接是具体开闭区间的解释,里面有我用ipad画的解释和样例,应该很清楚
实现选择开区间或闭区间的操作,输出开区间或闭区间 详解(线段树运用)

#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;

const ll N=132010;
const ll mod=100000000;

struct segment
{
    ll l,r;
    ll cov;//覆盖标记
    ll tag;//翻转标记
}tree[N<<2];

ll ans[N],n=N;

char s[20];

inline void get(ll &l,ll &r)
{
    char c=getchar();
    while(c!='('&&c!='[')c=getchar();
    ll w=(c=='(');l=0;
    while(!isdigit(c))c=getchar();
    while(isdigit(c))l=(l<<1)+(l<<3)+(c^48),c=getchar();
    l<<=1;//乘2是为了区分开区间还是闭区间
    l+=w;r=0;//是开区间说明要+1
    while(!isdigit(c))c=getchar();
    while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar();
    r<<=1;
    while(c!=')'&&c!=']')c=getchar();
    r-=(c==')');//处理开闭区间//是开区间说明需要-1
}

inline void push_down(ll p)
{
    if(tree[p].cov!=-1)
    {
        tree[ls].cov=tree[rs].cov=tree[p].cov;
        tree[ls].tag=tree[rs].tag=0;//先都初始化为0
        tree[p].cov=-1;
    }
    if(tree[p].tag)
    {
        tree[ls].tag^=1,tree[rs].tag^=1;//相同为0不同为1,把区间翻转
        tree[p].tag=0;
    }
}

inline void build(ll p,ll l,ll r)
{
    tree[p].l=l,tree[p].r=r,tree[p].cov=-1;
    if(l==r)return;
    ll mid=(l+r)>>1;
    build(ls,l,mid),build(rs,mid+1,r);
}

inline void update(ll p,ll l,ll r,ll k)
{
    if(tree[p].l>r||tree[p].r<l||l>r)return;
    if(tree[p].l>=l&&tree[p].r<=r)
    {
        if(k!=2)tree[p].cov=k,tree[p].tag=0;
        else tree[p].tag^=1;//需要的话就翻转
        return;
    }
    push_down(p);
    update(ls,l,r,k),update(rs,l,r,k);
}

inline void query(ll p)
{
    if(tree[p].l==tree[p].r)
    {//如果这个点没有被修改过那它一直都是0,否则就是看它当前被修改成了什么(cov),然后再异或上tag,翻转过后的值
        ans[tree[p].l]=(tree[p].cov==-1)?0:tree[p].cov^tree[p].tag;
        return;
    }
    push_down(p);
    query(ls),query(rs);
}

inline void print()
{
    query(1);
    int flag=0,Empty=0;
    for(int i=0;i<=n;++i)
    {
        if(ans[i]&&!flag)//遍历一遍,找到左边界
        {
            flag=Empty=1;
            if(i&1)printf("(%d,",(i-1)>>1);//如果是奇数说明是开区间
            else printf("[%d,",i>>1);
        }
        if(!ans[i]&&flag)
        {
            flag=0;
            if(i&1)printf("%d]\n",(i-1)>>1);//如果是奇数说明是闭区间
            else printf("%d)\n",i>>1);
        }
    }
    if(!Empty)
        puts("empty set");
}

int main()
{
    build(1,0,n);
    while(scanf("%s",s)!=EOF)//接受第一个字符
    {
        ll l,r;
        get(l,r);
        if(s[0]=='U')update(1,l,r,1);//U是求并集(区间内全部改为1)
        else if(s[0]=='I')update(1,0,l-1,0),update(1,r+1,n,0);//I是求交集(区间以外全部为0,不相交就全为0嘛)
        else if(s[0]=='D')update(1,l,r,0);//D是减去该区间,所以区间内全为0
        else if(s[0]=='C')update(1,0,n,2),update(1,0,l-1,0),update(1,r+1,n,0);//全集范围内01翻转,并将区间的补集覆盖0
        else update(1,l,r,2);//区间范围内01翻转
    }
    print();
    return 0;
}

120多行的代码debug是真的累

有任何疑问欢迎评论哦虽然我真的很菜