P5568 题解

题意简述

对一个空集 S 进行 M(M7×104) 次操作,每次给出一个集合 T(以自然数区间形式给出),对 S 进行以下五种操作之一:

  1. S=ST
  2. S=ST
  3. S=ST
  4. S=TS
  5. S=(ST)(TS)

求最终的 S

题目分析

很直观的线段树题,分建树、修改、查询分别展开说明。

建树

由于涉及到的区间都是自然数区间,因此需要维护的区间个数有限,用线段树的结点维护就可以了。

为了方便维护,我们可以将所有自然数自身组成的闭区间 [k,k](这种区间按理说是不合法的,但是本题都这么写了就将就用吧) 编号为 2k,将所有两个相邻自然数之间的开区间 (k,k+1) 编号为 2k+1。我们姑且称这两种区间为“单位区间”,则所有的自然数区间都可以表示为“单位区间”的并。例如 (a,b] 可以表示为编号 2a+12b 的“单位区间”之并,[b,a) 可以表示为编号 2a2b1 的“单位区间”之并。

我们考虑线段树结点维护的信息。最基本地,每个结点维护其对应区间的两端“单位区间”的编号 lr。而考虑到题目中的操作实质上就是改变了一些“单位区间”是否在答案区间里的状态,因此再让结点维护其对应区间中在 S 里的“单位区间”的个数 cnt。最后就是懒标记 tag,我们设计其可能值为 1,0,1,2,分别代表无标记、将区间内全部“单位区间”都从 S 中删除、将区间内全部“单位区间”都加入 S、将全部“单位区间”是否在 S 里的状态取反(可能有点奇怪,可先看修改部分再理解)。标记累加的具体细节详见代码。

修改

分析一下本题的五种操作:

对于操作 1,把 T 中的所有“单位区间”加入 S 里,即 T 对应的的结点 cnt 赋值为 rl+1

对于操作 2,把所有不在 T 中的“单位区间”从 S 中去除,即除 T 之外的区间对应的结点 cnt 赋值为 0

对于操作 3,把 T 中的所有“单位区间”从 S 中去除,即 T 对应的结点 cnt 赋值为 0

对于操作 4,把不属于 T 中的所有“单位区间”从 S 中去除,并把属于 T 中的所有“单位区间”状态取反即可。即除 T 之外的区间对应的结点 cnt 赋值为 0T 对应的结点 cnt 赋值为 rl+1cnt

对于操作 5,把属于 T 中的所有“单位区间”状态取反,即 T 对应的结点 cnt 赋值为 rl+1cnt

查询

题目要求输出在 S 中的区间,并不一定全都是结点对应的。但是注意到这些区间对应的所有结点一定满足 cnt=rl+1,所以我们考虑递归找到所有满足该条件的结点,再将其对应的区间合并即可。

我们从根结点开始查询。当前结点满足 cnt=rl+1,就把该结点对应的区间直接加到答案里,不再递归。否则,若左子结点的 cnt0,递归到左节点;若右子结点的 cnt0,则再递归到右节点。当然,如果最开始根结点 cnt=0,就输出 empty set

答案区间的合并是简单的,不再赘述。至于输出,根据区间的端点奇偶性判断区间开闭并计算原编号即可。

代码实现

#include<bits/stdc++.h>
using namespace std;
struct node
{
int l,r,cnt,tag;
}tr[524300];
char op[2],str[50];
int l[70010],r[70010],i,q,OP[70010],mx=INT_MIN,mn=INT_MAX,ans1[140010],ans2[140010],cnt,L;
bool pt(int l,int r)
{
printf("%c%d,%d%c ",(l&1?'(':'['),l>>1,r+1>>1,(r&1?')':']'));
}
void pushup(int p)
{
tr[p].cnt=tr[p<<1].cnt+tr[p<<1|1].cnt;
}
void addtag(int p,int val)
{
if(!val)
{
tr[p].tag=0;
tr[p].cnt=0;
}//如果是全改成 0,直接设置即可。
else if(val==1)
{
tr[p].tag=1;
tr[p].cnt=tr[p].r-tr[p].l+1;
}//全改成 1 也类似。
else//取反情况较复杂。
{
tr[p].cnt=tr[p].r-tr[p].l+1-tr[p].cnt;
if(tr[p].tag==2)//如果原来就要取反,再取反就不变了。
tr[p].tag=-1;
else if(tr[p].tag>-1)//如果原来是要全改成什么,取反后就是改成相反的数了 。
tr[p].tag=(!tr[p].tag);
else
tr[p].tag=2;//如果原来不改,就记上标记。
}
}//给结点 p 进行操作 val
void pushdown(int p)
{
if(tr[p].tag!=-1)
{
addtag(p<<1,tr[p].tag);//传给左子结点
addtag(p<<1|1,tr[p].tag);//传给右子结点
tr[p].tag=-1;//清空标记
}
}
void build(int p,int l,int r)
{
tr[p].l=l,tr[p].r=r;
tr[p].tag=-1;//初始没有标记。
if(l==r)
{
tr[p].cnt=0;//初始 S 为空
return;
}
int mid=l+r>>1;
build(p<<1,l,mid);//建左子节点
build(p<<1|1,mid+1,r);//建右子节点
pushup(p);
}
void change(int p,int l,int r,int val)
{
if(tr[p].l>=l&&tr[p].r<=r)
{
addtag(p,val);//当前结点全在修改区间里,直接改。
return;
}
pushdown(p);//先传标记
int mid=tr[p].l+tr[p].r>>1;
if(l<=mid)//左子结点要改
change(p<<1,l,r,val);
if(r>mid)//右子结点要改
change(p<<1|1,l,r,val);
pushup(p);
}
void query(int p)
{
pushdown(p);//先传标记。
if(tr[p].cnt==tr[p].r-tr[p].l+1)
{
ans1[++cnt]=tr[p].l;
ans2[cnt]=tr[p].r;//把结点的左右编号放到答案里
return;
}
if(tr[p<<1].cnt)
query(p<<1);//左子结点有在 S 里的就查询
if(tr[p<<1|1].cnt)
query(p<<1|1);//右子结点有在 S 里的就查询
}
int main()
{
while(scanf("%s %s",op+1,str+1)==2)
{
q++;
l[q]=r[q]=0;
for(i=2;str[i]<='9'&&str[i]>='0';i++)
l[q]=l[q]*10+str[i]-'0';
for(i++;i<strlen(str+1);i++)
r[q]=r[q]*10+str[i]-'0';
if(str[1]=='[')
l[q]=2*l[q];
else
l[q]=2*l[q]+1;
if(str[strlen(str+1)]==']')
r[q]=2*r[q];
else
r[q]=2*r[q]-1;
if(op[1]=='U')
OP[q]=1;
else if(op[1]=='I')
OP[q]=2;
else if(op[1]=='D')
OP[q]=3;
else if(op[1]=='C')
OP[q]=4;
else
OP[q]=5;
mx=max(mx,r[q]);
mn=min(mn,l[q]); //省空间,只在修改涉及到的最小值到最大值之间建树即可
}
mn--;//注意修改时会涉及到 mn-1 和 mx+1
mx++;
build(1,mn,mx);
for(int i=1;i<=q;i++)
{
if(OP[i]==1)
change(1,l[i],r[i],1);
else if(OP[i]==2)
change(1,mn,l[i]-1,0),change(1,r[i]+1,mx,0);
else if(OP[i]==3)
change(1,l[i],r[i],0);
else if(OP[i]==4)
change(1,mn,l[i]-1,0),change(1,l[i],r[i],2),change(1,r[i]+1,mx,0);
else
change(1,l[i],r[i],2);
}
if(!tr[1].cnt)//S 为空
{
printf("empty set");
return 0;
}
query(1);//查询
L=ans1[1];
for(int i=2;i<=cnt;i++)
if(ans1[i]-ans2[i-1]!=1)
{
pt(L,ans2[i-1]);
L=ans1[i];
}
pt(L,ans2[cnt]);//输出
return 0;
}
posted @   Hadtsti  阅读(97)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示