LogLoader是一家专门提供日志分析产品的公司。Ikki在做毕业设计的同时,还忙于在LogLoader做实习。在他的工作里,有一项是要写一个模块来处理时间区间。这个事情一直让他感到很迷糊,所以现在他很需要你帮忙。
在离散数学里面,你已经学习了几种基本的集合运算,具体地说就是并、交、相对补和对称差。它们自然地也适用于区间这种特殊的集合。作为你的快速参考,它们可以总结成下表:
运算 记号
定义
并 A ∪ B {x : x ∈ A或x ∈ B}
交 A ∩ B {x : x ∈ A并x ∈ B}
相对补 A − B {x : x ∈ A但是x ∉B}
对称差 A ⊕ B (A − B) ∪ (B − A)
Ikki已经把他的工作里出现的区间运算抽象成一个很小的编程语言。他想你为他实现一个解析器。这个语言维护一个集合S。S一开始是空集,并根据下列命令被修改:
命令 语义
U T S ← S ∪ T
I T S ← S ∩ T
D T S ← S − T
C T S ← T − S
S T S ← S ⊕ T
Input
输入包含一组测试数据,由0到65,535条命令组成。每条命令占一行,形式如下:
X T
其中X是‘U’、‘I’、‘D’、‘C’和‘S’中的一个,T是一个区间,形式为(a,b)、(a,b]、[a,b)和[a,b]之一(a, b ∈ Z; 0 ≤ a ≤ b ≤ 65,535),取它们通常的意义。命令按在输入中出现的顺序执行。
文件结束符(EOF)表示输入结束。
Output
以一组不相交区间的并的形式输出在最后一条命令执行之后的集合S。这些区间在一行内输出,由单个空格分隔,按端点的升序排序。如果S是空集,输出“empty set”。
Sample Input
U [1,5]
D [3,3]
S [2,4]
C (1,5)
I (2,3]
Sample Output
(2,3)
思路:线段树
1.首先我们需要知道这五个操作在线段树中是如何执行的。
U 就是把[l,r]这一段全部变成0 //线段树操作:成段更新
I 就是把[0,l)(r,maxn]这一段全部变成0 //成段更新
D 就是把[l,r]这一段变成0 //成段更新
C 就是把[0,l)(r,maxn]这一段全部变成0(成段更新) 把[l,r] 0/1进行互换(成端更新,异或操作)
S 就是把[l,r] 0/1进行互换(成端更新,异或操作)
好了,我们现在已经理清楚第一步了,涉及成端更新,我们就用到了懒惰标记,这里我们需要维护两个值。
cover数组(标记)维护这一段当前的值(-1表示既有包括也有不包括[即区间不连续],0表示该区间不包括,1表示该区间包括)
XOR数组(标记)维护当前区间是否需要进行异或操作,0表示不需要,1表示不需要。(t^=1).
这两个标记如何处理:
1).当某段区间获得cover标记的话,那么意味这之前这段区间无论有没有xor标记,现在都可以直接清除掉。
2).当某段区间获得xor标记的话,先判断其覆盖标记是0或者1的话,我们直接对该段区间(线段树某节点)进行异或操作,
否则的话,改变异或标记。
以上就是线段树的所有操作了,现在还有一个问题,区间是有开区间和闭区间之分的,我们如何来区分区间的开闭呢。
我们把偶数表示端点,奇数表示两个端点之间的那一段区间即可。
线段树中,0表示[0,0],那么1为(0,1),2为[1,1],3为(1,2),4为[2,2],5为(2,3),6为[3,3]
即i为奇数时表示(i/2,i/2+1)(两个端点之间的区间),i为偶数时表示[i/2,i/2](端点).
大概就这些点了。
public class Main {
// U [l,r]->1
// I [0,l)(r,maxn]->0
// D [l,r]->0
// C [0,l)(r,maxn]->0 [l,r] 0/1(^)
// S [l,r] 0/1(^)
public static final int MAXN=(65535<<1);//大小要开两倍
public static int[] XOR; //维护异或标志
public static int[] cover; //维护覆盖标志
public static boolean[] flag;
public static void FXOR(int rt){
if(cover[rt]!=-1){ // 0/1互相替换
cover[rt]^=1; //整段为0替换成1 整段为1替换成0,若01并存的话,那就更新异或标志
}else{
XOR[rt]^=1;//如果该段区间既存在包含又存在不包含的改变异或标志
}
}
//先检查覆盖标志再检查异或标志
public static void pushDown(int rt){
if(cover[rt]!=-1){ //向下更新
cover[rt<<1]=cover[rt<<1|1]=cover[rt];
XOR[rt<<1]=XOR[rt<<1|1]=0; //只要覆盖,无论之前是否有异或标志都要清除
cover[rt]=-1;
}
if(XOR[rt]!=0){
FXOR(rt<<1);
FXOR(rt<<1|1);
XOR[rt]=0;
}
}
public static void update(char op,int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
switch (op){ //这里就是上面说的 对五种情况进行判断 做不同处理
case 'U':cover[rt]=1;XOR[rt]=0;break;
case 'D':cover[rt]=0;XOR[rt]=0;break;
case 'S': case 'C':FXOR(rt);
}
return;
}
pushDown(rt);
int mid=(l+r)>>1;
if(L<=mid){
update(op,L,R,l,mid,rt<<1); //区间内操作
}else if(op=='I'||op=='C'){ //I C需要对区间外进行操作
cover[rt<<1]=XOR[rt<<1]=0; //区间外变成0
}
if(R>mid){
update(op,L,R,mid+1,r,rt<<1|1);
}else if(op=='I'||op=='C'){
cover[rt<<1|1]=XOR[rt<<1|1]=0;//区间外变成0
}
}
public static void query(int l,int r,int rt){
if(cover[rt]==1){ //进行区间覆盖标志判断
for(int i=l;i<=r;++i){
flag[i]=true; //标记该节点所包含的这一段区间
}
return;
}else if(cover[rt]==0){
return;
}
if(l==r){
return;
}
pushDown(rt);
int mid=(l+r)>>1;
query(l,mid,rt<<1);
query(mid+1,r,rt<<1|1);
}
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
//Scanner cin=new Scanner( InputUtil.cin());
Scanner cin=new Scanner(new InputStreamReader(System.in));
XOR=new int[MAXN<<2];
cover=new int[MAXN<<2];
flag=new boolean[MAXN+1];
cover[1]=XOR[1]=0;
while (cin.hasNext()){
String op=cin.next();
String f=cin.next();
int a=Integer.parseInt(f.substring(1,f.lastIndexOf(',')));
int b=Integer.parseInt(f.substring(f.lastIndexOf(',')+1,f.length()-1));
char l=f.charAt(0);
char r=f.charAt(f.length()-1);
a<<=1;b<<=1; //乘以2 映射到线段树中 奇数表示两端点间的那一段区间 偶数表示端点
if(l=='('){
++a;
}
if(r==')'){
--b;
}
if(a>b){ // (3,3)
if(op.charAt(0)=='C'||op.charAt(0)=='I'){
XOR[1]=cover[1]=0; //直接整个区间变成0
}
}else{
update(op.charAt(0),a,b,0,MAXN,1);
}
}
query(0,MAXN,1);
boolean f=false;
int st=-1;
int ed=0;
for(int i=0;i<=MAXN;++i){
if(flag[i]){
if(st==-1){
st=i;
}
ed=i;
}else{
if(st!=-1){
if(f){
System.out.print(" ");
}
f=true;
StringBuilder sb=new StringBuilder();
char l=(st&1)==1?'(':'['; //判断节点奇偶性 偶数为闭区间 奇数为开区间
char r=(ed&1)==1?')':']';
sb.append(l).append(st>>1).append(',').append((ed+1)>>1).append(r);
//左边是奇数不需要+1 右边是奇数我们需要+1偏向大的区间 [6,7)->[3,4) (5,6]->(2,3]
System.out.print(sb.toString());
st=-1;
}
}
}
if(!f){
System.out.println("empty set");
}
}
}