一个AC一把泪……(看了几篇大牛的博客才搞定)
这个题目就两个关键点,搞明白就没什么问题:
1.关于集合运算的推导规约,知道集合是什么东西就一定会推导!
U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换
2.倍化区间处理开闭区间的问题。因为普通的线段树实际处理的并非真正的区间,而是一系列点,相当于处理一个向量。这个问题需要处理的是真正的区间,所以应该有一个主导思想就是,把区间点化!不知哪位大牛搞了一个倍增区间出来,实在佩服!对于待处理区间[a,b](暂时不考虑开闭),对其边界均乘2。若区间左开则对左界值+1,若区间右开,则对右界-1!
如:[2,3]会倍增为[4,6],[2,3)会倍增为[4,5],(2,3]会倍增为[5,6],(2,3)将倍增为[5,5],我们这时可以看到,对于普通线段树无法处理的线段如(x,x+1)将被点化为[2*x+1,2*x+1]!这个问题得到比较完美的解决
最后把查找出来的区间逆向倍增操作一下,就可以得到实际的区间以及起开闭情况!
代码中还将用到延迟更新,向子节点更新操作时,这个具体纠结在互换上面,不过仔细想想还是容易理解的,下面代码会有注解!
区间倍增后,就是处理普通线段树了,这时候一定要思路清晰!
当然普通线段树有两种,1).一种是一开一闭的形式,兄弟节点看上去好像连接起来了(由于开闭是固定的,所以无法直接来处理该题):如对于父节点[a,b),其左孩子为[a,(a+b)/2),右孩子为[(a+b)/2,b)……2).另一种是全闭的形式,对于父节点[a,b],其左孩子为[a,(a+b)/2],右孩子为[(a+b)/2+1,b]……这时候一定要看清本质,不要被倍增区间搞混了!
注:题目中还要处理一些无效输入如(4,4)这种没有意义的区间
代码如下:
/*U:把区间[l,r]覆盖成1
I:把[-∞,l)(r,∞]覆盖成0
D:把区间[l,r]覆盖成0
C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
S:[l,r]区间0/1互换*/
#include<cstdio>
#include<iostream>
using namespace std;
#define MAXLEN 65536<<1 //倍增区间长度
int Cover[MAXLEN<<2]; //4倍的线段树的覆盖标记空间
int Reversal[MAXLEN<<2]; //4倍的线段树的翻转标记空间
struct
{
char l, r;
int a, b;
}Interval[MAXLEN+1]; //区间序列
int Count; //区间计数器
void push_down(int num) //延迟更新中用于向子节点更新信息
{
if(Cover[num]!=-1) //当前节点获得覆盖标记
{
Cover[num<<1]=Cover[num<<1|1]=Cover[num]; //覆盖标记传递给子节点
Reversal[num<<1]=Reversal[num<<1|1]=0; //清除子节点翻转标记
Cover[num]=-1; //当前节点执行覆盖完成,清除覆盖标志
}
if(Reversal[num]) //当前节点获得翻转标记
{
if(Cover[num<<1]!=-1) Cover[num<<1]^=1; //左子节点持有覆盖标记,覆盖标记取反
else Reversal[num<<1]^=1; //翻转标记取反
if(Cover[num<<1|1]!=-1) Cover[num<<1|1]^=1;
else Reversal[num<<1|1]^=1;
Reversal[num]=0; //当前节点执行翻转完成,清除翻转标志
}
}
void update_info(int op,int l,int r,int LB,int RB,int num)
{
if( l > r || l < 0) return ; //异常区间直接返回
if(l==LB&&r==RB)
{
if(op!=-1) //当前操作为覆盖操作,直接覆盖
{
Cover[num]=op;
Reversal[num]=0;
}
else //当前操作为翻转操作
{
if(Cover[num]!=-1) Cover[num]^=1;
else Reversal[num]^=1;
}
return ;
}
push_down(num); //当前节点信息向下更新
int MID=(LB+RB)>>1;
if(r<=MID) update_info(op,l,r,LB,MID,num<<1); //修正左子树
else if(l>=MID+1) update_info(op,l,r,MID+1,RB,num<<1|1); //修正右子树
else
{
update_info(op,l,MID,LB,MID,num<<1);
update_info(op,MID+1,r,MID+1,RB,num<<1|1);
}
}
void Query(int LB,int RB,int num) //区间查询
{
if(Cover[num]==1)
{
if(LB%2) Interval[Count].l='(';
else Interval[Count].l='[';
Interval[Count].a=LB>>1; //此处左开+1在右移除2时自动丢掉了
if(RB%2) Interval[Count].r=')';
else Interval[Count].r=']';
Interval[Count].b=(RB+1)>>1; //此处+1是补偿右开时的-1
Count++;
}
else if(Cover[num]==0) return;
else
{
push_down(num);
int MID=(LB+RB)>>1;
Query(LB,MID,num<<1);
Query(MID+1,RB,num<<1|1);
}
}
void print(void) //输出区间
{
char l,r;
int a,b,i=0;
if(Count==0) cout<<"empty set"<<endl;
else
{
l=Interval[0].l;
r=Interval[0].r;
a=Interval[0].a;
b=Interval[0].b;
for(i=1;i<Count;i++)
{
if(b==Interval[i].a&&(r==']'||Interval[i].l=='['))
{
b=Interval[i].b;
r=Interval[i].r;
}
else
{
cout<<l<<a<<','<<b<<r<<' ';
l=Interval[i].l;
r=Interval[i].r;
a=Interval[i].a;
b=Interval[i].b;
}
}
cout<<l<<a<<','<<b<<r<<' ';
}
}
int main(int argc,char* argv)
{
int a,b;
char op,l,r;
Cover[1]=Reversal[1]=0; //初始化树
while ( ~scanf("%c %c%d,%d%c\n",&op , &l , &a , &b , &r) ) //返回EOF时结束(对值的补码取反)
{
a<<=1; //区间倍增
b<<=1; //区间倍增
if(l=='(') a++; //左开+1
if(r==')') b--; //右开-1
if(op=='U') update_info(1,a,b,0,MAXLEN,1); //集合覆盖
else if(op=='I') //集合交集
{
update_info(0,0,a-1,0,MAXLEN,1);
update_info(0,b+1,MAXLEN,0,MAXLEN,1);
}
else if(op=='D') update_info(0,a,b,0,MAXLEN,1); //集合正差
else if(op=='C') //集合逆差
{
update_info(0,0,a-1,0,MAXLEN,1);
update_info(0,b+1,MAXLEN,0,MAXLEN,1);
update_info(-1,a,b,0,MAXLEN,1);
}
else update_info(-1,a,b,0,MAXLEN,1); //集合异或
}
Query(0,MAXLEN,1);
print();
return 0;
}
PS:没看清题目让结果用空格隔开,我隔行输出,果断PE了!幸亏没有报WA,不然我可能到死也不知道哪里错了……一定要读清题目!