poj 3225

这题是用线段树解决区间问题,看了两天多,算是理解一点了。

Description

LogLoader, Inc. is a company specialized in providing products for analyzing logs. While Ikki is working on graduation design, he is also engaged in an internship at LogLoader. Among his tasks, one is to write a module for manipulating time intervals, which have confused him a lot. Now he badly needs your help.

In discrete mathematics, you have studied several basic set operations, namely union, intersection, relative complementation and symmetric difference, which naturally apply to the specialization of sets as intervals.. For your quick reference they are summarized in the table below:


Ikki has abstracted the interval operations emerging from his job as a tiny programming language. He wants you to implement an interpreter for him. The language maintains a set S, which starts out empty and is modified as specified by the following commands:

Input

The input contains exactly one test case, which consists of between 0 and 65,535 (inclusive) commands of the language. Each command occupies a single line and appears like

X T

where X is one of ‘U’, ‘I’, ‘D’, ‘C’ and ‘S’ and T is an interval in one of the forms (a,b), (a,b], [a,b) and [a,b] (a, b ∈ Z, 0 ≤ a ≤ b ≤ 65,535), which take their usual meanings. The commands are executed in the order they appear in the input.

End of file (EOF) indicates the end of input.

Output

Output the set S as it is after the last command is executed as the union of a minimal collection of disjoint intervals. The intervals should be printed on one line separated by single spaces and appear in increasing order of their endpoints. If S is empty, just print “empty set” and nothing else.

Sample Input

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

(2,3)

大意就是刚开始集合S为空,输出经过一些操作后的集合S。

说实话刚开始看胡浩大牛精炼的解释没看懂。他的代码里都没有建树的过程。不是线段树的题吗?树呢?怎么没有看到Build()函数?慢慢才发现,树就在无形之中啊!先来看怎么处理这些集合操作:

  初始时集合S为空,假设所有区间都为0代表不在集合S里面,区间赋值为1代表区间在S里面,区间赋值为-1代表区间有一部分在有一部分不在S里面,

  U:并集操作,这个好说,直接把读入的区间[l,r]置为1,表示并入集合S。

I:交集操作,S ∩T,T=[l,r]。这里就需要考虑一下,既然是交集,就是公共部分,容易想直接把[l,r]置成1,但是显然这是不对的,这样就是并的操作了。考虑一个例子帮助理解:S=[2,5],T=[3,6]。所以S ∩T=[3,5]。因为S、T有不相交的部分,所以可以采用类似补集或者对立面的想法,我既然不能直接把T区间赋值,那就看看能不能把T的补集赋值,可以想到,这时应该把[-∞,l),(r,+∞]赋值为0。那么剩下的必然会包括S ∩T。

  D:S=S-T。从S中去掉T区间,简单,直接T区间赋值为0。

  C:S=T-S。这个和D操作正好相反。是从T中去掉S。根据上面的例子,可以想到:应该把[-∞,l),(r,+∞]赋值为0,[l,r]区间0/1互换。互换后要保证结果正确需要用到下面说到的xor(异或)

  S:S=S⊕T。这个相当于C+D。还是用上面的例子,S=[2,5],T=[3,6]。刚开始S区间是赋值1的,T区间是赋值0的。这个操作相当于求S、T并的部分减去交的部分。那么我其实就可以把T与S交的部分变成0,不交的部分置成1.所以可以直接把[l,r]区间0/1互换。

下面来考虑怎样处理区间开闭问题,其实想想要是在每次操作还要考虑区间开闭的话会很麻烦。所以每次读入数据就把区间*2,若左开就左++,右开就右减减。比如区间本来是(2,4]变成[5,8],[2,4)变成[4,7],这样区间的开闭就有端点的奇偶性来表示了,也可以理解为一种映射吧,最后输出结果时只需要按照操作反向输出就好了。

  最后,代码需要开一个cover[]数组记录区间状态(1,0,-1),关于异或操作,就可以另开一个Xor[]数组记录。这两个其实都相当于lazy标记,而它们也正是这棵无形的线段树!我觉得这比基础的线段树直接高了一个档次,还需要好多时间去理解线段树!而且正好这道题给定a,b在[0,65535]范围内,那么区间扩倍就是65535*2,所以存储[0,65535]区间最终状态的bo[]数组只需开到65535*2,cover[]数组、Xor[]数组只需开到65536*2*4,竟是如此精确!

  记录一下自己一直很迷惑的地方:就是为什么Update()函数中在完全包含区间内有异或操作时(Fxor()函数)只有cover[rt]!=-1时才执行异或操作,否则异或Xor[rt]。cover[rt]!=-1时执行异或很显然,因为这时这段区间不是0就是1,直接异或就可以,而cover[rt]=-1时,首先应该知道-1xor1=-2,这道题cover[]根本不会等于-2,再说这样xor出来的值无法代表下面区间到底是1还是0,所以此时需要的操作是Xor[rt]^=1,举例说明:比如当前rt代表[1,10],且cover[rt]=-1,也就是[1,10]区间有0也有1,如果直接想把[1,10]里面0/1互换,直接cover[rt]^=1根据上面可知是不行的,但是要是Xor[rt]^=1就完美解决了问题。因为此时不管原来[1,10]中Xor[rt]为0还是为1,对它异或一下就相当于互换了所表示区间的0/1值。这样也就可以理解了为什么在PushDown()下方标记时先看cover[rt]是否为-1,再看Xor[rt]是否需要异或。当cover[rt]=-1且Xor[rt]=1时,说明这段区间需要异或且区间内有0也有1,所以再继续看这个区间的左右区间cover[]标记,不为-1直接异或,否则还是异或子区间的Xor[]标记......,这就相当于标记下推了嘛。(其实应该不难理解,就是自己一直没有往深处再想想,而且看别人解释时只看到了表面)

还有其它的一些细节可以参考以下博客:http://blog.csdn.net/harlow_cheng/article/details/52483783,这位博主写的非常详细,无需我多言了。

代码注释好多也是直接用的他的,一小部分是我新加的,在此感谢那位博主~:

 #include<cstdio>
#include<cstring>
#define mid int m=(l+r)>>1//宏定义什么的,使程序简练些
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
const int maxn = * ;//树的大小,此题真的是无形建树,最为致命!
int cover[maxn << ], Xor[maxn << ];//两个lazy标签,一个表示当前区间所处状态:全在集合中(1),全不在集合中(0),有的在有的不在(-1);另一个b表示当前区间是否需要异或
int bo[maxn+]; void Fxor(int rt)//异或操作
{
if (cover[rt] != -)//如果这个区间之前已经被整个覆盖了值。
cover[rt] ^= ;//那么就直接在覆盖值上异或一下就可以了。
else//如果之前没有覆盖值。
Xor[rt] ^= ;//那么就在它的异或标记上进行操作。
} void PushDown(int rt)
{
if (cover[rt] != -)//如果这个节点整个被覆盖了某个值
{
cover[rt << ] = cover[rt << | ] = cover[rt];//那么就把覆盖的值整个往儿子们传递。
Xor[rt << ] = Xor[rt << | ] = ;//因为覆盖了值。所以之前的异或标记也就没有意义了。直接置为0;
cover[rt] = -;//之后可能会对这个节点所代表的区间进行操作。所以它不再是确定的了。
}
else if (Xor[rt]>) {//如果被覆盖了某个值,那么就不可能有异或标记的所以这里写的是else ,如果这个节点所代表的区间需要被异或。
if (cover[rt << ] != -) {//如果左区间被完全覆盖了某个值
cover[rt << ] ^= ;//那么就把覆盖的值异或
}
else
Xor[rt << ] ^= ;//如果没有被完全覆盖,则把异或标记异或一下。
if (cover[rt << | ] != -) {//右区间同理。
cover[rt << | ] ^= ;
}
else
Xor[rt << | ] ^= ;
Xor[rt] = ;//然后这个节点的异或标记重置一下。
}
} void Update(char op, int L, int R, int l, int r, int rt)//操作符是op,当前节点rt所代表的区间是L,R。然后
{//所要操作的区间是(l,r)
if (L <= l&&r <= R) //如果这个节点的区间整个被覆盖在所需要操作的区间内。
{
if (op == 'U') {//如果是并集操作
cover[rt] = ; Xor[rt] = ;//直接把这一段覆盖为1
}
else if (op == 'D') {//如果是D操作,则直接把这段区间覆盖为0.x属于S且x不属于T
cover[rt] = ; Xor[rt] = ;
}
else if (op == 'C' || op == 'S') { //这是两个需要把操作区间整个异或的操作。
Fxor(rt);
}
return;//进行标记之后就不要往下走了。直接结束。
}
PushDown(rt);//处理这个节点的懒惰标记。
mid;//取得中点
if (L <= m) Update(op, L, R, lson);//往左逼近那个交集区间。
else if (op == 'I' || op == 'C') {//如果左儿子所代表的区间完全不在所求的区间内,则按照操作判断是否置0。
cover[rt << ] = Xor[rt << ] = ;
}
if (R>m) Update(op, L, R, rson);//往右逼近交集区间
else if (op == 'I' || op == 'C') {//右儿子同理。
cover[rt << | ] = Xor[rt << | ] = ;
}
} void Query(int l, int r, int rt)
{
if (cover[rt] == )//如果当前到的这个节点被完全覆盖了
{
for (int i = l; i <= r; i++)//就标记这个区间都在集合内。
bo[i] = true;
return;
}
else if (cover[rt] == ) return;//如果全部不覆盖则退出
//注意!!如果cover[rt]==-1,是不能退出的,还要往下找,找到能够一概而论的节点。
if (l == r) return;//如果左右断点都相同了则结束。
PushDown(rt);//处理懒惰标记什么的。
mid;
Query(lson);
Query(rson);
} int main()
{
char op, l, r;//操作符 左括号 右括号
int a, b;
cover[] = Xor[] = ;//相当于两个lazy标签,初始时集合为空,且没有标记,所以都设为0
while (~scanf(" %c %c%d,%d%c", &op, &l, &a, &b, &r))//读入数据,用空白符去掉回车符
{
a = a << ; b = b << ;//区间扩倍
if (l == '(') a++;//处理开区间
if (r == ')') b--;//同上
if (a>b) {//处理例如(1,1)这样的空集
if (op == 'I' || op == 'C')//'I'、'C'操作有处理补集的,空集的补集就是全集
cover[] = Xor[] = ;//相当于整棵树被初始化
}
else Update(op, a, b, , maxn, );//否则更新树
}
memset(bo, false, sizeof(bo));//标记某个位置是否在集合内
Query(, maxn, );//查询那些点在集合中
bool flag = false;//判断是否要输出空格,可能有多个区间
int s = -, e;
for (int i = ; i <= maxn; i++)//扫描整个区间
{
if (bo[i]) {//如果点在集合内
if (s == -) s = i;//是起点就记录
e = i;
}
else if (s != -) {//如果遇到了一个不在集合内的点就判断之前是不是找到了连续的点组成了集合
if (flag) printf(" ");//如果之前已经输出过答案了则要输出空格
flag = true;//记录已经找到一个集合了。
printf("%c%d,%d%c", s & ? '(' : '[', s >> , (e + ) >> , e & ? ')' : ']');
s = -;//如果两端的端点某一个是奇数,则就是开区间,然后根据输入时的规则逆向想一下。然后除2就可以了。
}
}
if (!flag)//如果都没输出过集合。那就是空集了。
printf("empty set");
return ;
}

poj 3225

这里记录一下那个博主写的题解,万一博客关闭了呢>_<.

【题解】

这题要用线段树来解。具体的解法:

U:因为是求并集。所以只要在原来S的基础上把(l,r)区间全部覆盖(置为1)就可以了。

I:求交集,X属于S,且X属于T,那只需要把除了(l,r)之外的所有区间都置为0就可以了。

D:X属于S,且X不属于T,则只要把(l,r)这个区间全部置为0就可以了。

C:X属于T,且X不属于S,这个有点麻烦,首先要把除了(l,r)的全部置为0,然后把(l,r)这段区间的0改为1,1改为0;这样才能满足要求;

S:其实S就是C+D的升级版。(X属于T且X不属于S)并上(X属于S且X不属于T),前者和C一样,然后后者和D一样,那么就只要进行C的后半部分操作就可以了。即只要把(l,r)这段区间的0改为1,1改为0就行了。

具体的做法:

【】

一开始读入数据的时候,不管读入的区间a,b的括号是怎么样的。

都先乘上2

然后如果左括号是开区间

则a++;

如果右括号是开区间。

则b--;

最后输出区间的时候如果端点是奇数则是开区间;

然后对于左端点除2再输出即可,对于右端点则需要先加上1再除2.

【】

设cover[rt]表示rt这个节点所代表的区间是否在集合中。

如果

cover[rt]==,表示rt这个节点所代表的区间全部在集合中。

cover[rt] == ,表示rt这个节点所代表的区间全部都不在集合中。

cover[rt]==-,表示rt这个节点所代表的区间不能一概而论(有些可能在,有些可能不在)。

设XOR[rt]表示rt这个节点是否要进行异或操作。

【】

当发现rt这个节点所代表的区间在所需要覆盖的区间范围内的时候。

先查看一下操作是什么。

如果是覆盖操作(即全部置为0或全部置为1),那么异或操作就没有意义了。

因为不管你之前怎么异或。我当前都只要把它改成一个特定的值了。

那么重新记录一下cover[rt]==需要覆盖的值。

然后XOR[rt]置为0,表示不需要再进行异或操作了。

然后如果是异或操作。

则需要看一下cover[rt]是不是-,如果cover[rt]是-,则说明这段区间没有被全部覆盖成相同的值。那么就只需要对XOR[rt]进行一下异或就行了。具体的如果XOR[rt]当前是1,就改为0,如果是0就改为1;

可以XOR[rt]^=,又或者写成XOR[rt] = -XOR[rt];

然后如果cover[rt]不是-,就说明这段区间被覆盖了相同的值。那么异或一下就可以了。

即cover[rt]^= (或者cover[rt]=-cover[rt]);

然后直接退出当前的递归。

这些数据可以作为懒惰标记使用。

【】

紧接着我们需要处理懒惰标记。

如果当前这个节点的cover值不为-,就说明整个区间都会被覆盖成相同的值。那么这个时候异或标记是肯定为0的。因为如果cover标记存在,那么异或操作就不会对XOR数组进行操作了。这样的话,就只需要把两个儿子节点覆盖成和父亲节点一样的值就可以了。

然后把cover[rt]改为-,表示下面已经不确定了。(因为如果访问了这个节点,我们可能就会对这个节点下面的部分进行修改了,不能够一概而论了。);

然后如果这个节点的cover值为-,就看一下异或标记是否大于0,如果大于0;

则对儿子节点进行相同的判断(判断是否cover[儿子]为-....);然后把异或标记置为0;

【】

还有一个问题就是如何把区间外的部分置为0?

可以这样

在进行成段更新的时候

m=(begin+end)>>;

正常情况下我们这样写

if ( l <= m)

updata(l,r,num,begin,end,rt<<);

if (m <r)

updata(l,r,num,m+,end,rt<<|);

我们这样写是为了不断地逼近有交集的那个部分。

但是

if (l >m)

则说明,我们索要求的l,r区间完全在左儿子所代表的区间的右边。

那么我们就可以在特定需要的情况下进行一个操作即

cover[rt<<] == ,XOR[rt<<] =;

这样就完成了对不在所操作的区间内的区间的覆盖0问题;右儿子也同理。所以代码就变成这样了。

int m = (begin + end) >> ;
if (l <= m)
updata(op, l, r, begin, m, rt << );
else
if (op == 'I' || op == 'C')
cover[rt << ] = XOR[rt << ] = ;
if (m < r)
updata(op, l, r, m + , end, rt << | );
else
if (op == 'I' || op == 'C')
cover[rt << | ] = XOR[rt << | ] = ;

题解

poj 3225 【线段树】的更多相关文章

  1. poj 3225 线段树+位运算

    略复杂的一道题,首先要处理开闭区间问题,扩大两倍即可,注意输入最后要\n,初始化不能随便memset 采用线段树,对线段区间进行0,1标记表示该区间是否包含在s内U T S ← S ∪ T 即将[l, ...

  2. POJ 3225 线段树区间更新(两种更新方式)

    http://blog.csdn.net/niuox/article/details/9664487 这道题明显是线段树,根据题意可以知道: (用0和1表示是否包含区间,-1表示该区间内既有包含又有不 ...

  3. POJ 3225 (线段树 区间更新) Help with Intervals

    这道题搞了好久,其实坑点挺多.. 网上找了许多题解,发现思路其实都差不多,所以就不在重复了. 推荐一篇比较好的题解,请戳这. 另外,如果因为可能要更新多次,但最终查询只需要一次,所以没有写pushup ...

  4. POJ 3225 线段树+lazy标记

    lazy写崩了--. 查了好久 /* U-> [l,r]–>1 I-> [1,l-1] [r+1,+无穷] –>0 D-> [l,r]–>0 C-> [1,l ...

  5. poj 2886 线段树+反素数

    Who Gets the Most Candies? Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 12744   Acc ...

  6. poj 3468(线段树)

    http://poj.org/problem?id=3468 题意:给n个数字,从A1 …………An m次命令,Q是查询,查询a到b的区间和,c是更新,从a到b每个值都增加x.思路:这是一个很明显的线 ...

  7. POJ——3264线段树

    题目: 输入两个数(m,n),m表示牛的头数,n表示查询的个数.查询时输入两个数(x,y),表示查询范围的起始值和终止值,查询结果是,这个区间内牛重量的最大值减去牛重量的最小值,数量级为1000,00 ...

  8. POJ 2828 线段树(想法)

    Buy Tickets Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 15422   Accepted: 7684 Desc ...

  9. poj 2828 线段树

    http://poj.org/problem?id=2828 学到的思维: 1.变化的或者后来的优先影响前面的,那么从最后一个往前看,最后一个就成了 确定的, 而且后来的也能够确定----假设从前往后 ...

  10. POJ - 1177 线段树

    POJ - 1177 扫描线 这道题也算是一道扫描线的经典题目了. 只不过这道题是算周长,非常有意思的一道题.我们已经知道了,一般求面积并,是如何求的,现在我们要把扫描线进行改造一下,使得能算周长. ...

随机推荐

  1. 【vue移动端架子】vue-h5-template

    作者大大的地址:https://github.com/sunnie1992/vue-h5-template 我们运行项目,倒是可以看一看效果 虽然就是显示的UI,但是应该可以知道作者大大想要什么东西了 ...

  2. tmux使用教程

    1.安装 2.操作 如何操作快捷键呢? 比如新建一个窗口的命令是:ctrl+b+c 那么,先按住ctrl不放,接着按下b键,然后ctrl和b键都完全松开后,再立马按下c键. 3.使用命令行 tmux ...

  3. Django项目:CRM(客户关系管理系统)--46--38PerfectCRM实现全局账号登录注销01

    python.exe manage.py startapp gbacc #urls.py """PerfectCRM URL Configuration The `url ...

  4. 洛谷2593 [ZJOI2006]超级麻将——可行性dp

    题目:https://www.luogu.org/problemnew/show/P2593 发现三个连续牌的影响范围只有3.相同牌的影响范围只有1之后就可以dp了. O(100^7)T飞. #inc ...

  5. Git 对已经加入版本控制的文件,修改后希望不被提交办法

    参考网址:http://my.oschina.net/zlLeaf/blog/197740 问题举例:假设网站有一个数据库配置文件db.php,通过git做版本控制,已经将这个文件提交到git库中.但 ...

  6. 【笔记】LR11中关联设置

    LR中关联建议都手动进行,自动不好用,也容易出错. 在LR中我们什么要做关联:1.关联解决的是动态数据的参数化.2.关联的数据一定是服务器响应的数据.3.服务器响应过来的数据在后面的服务还要使用. 手 ...

  7. 20190716-T1-礼物

    呵呵,我暴力WA了 这个题充分考验了大家对数学的理解(麦蒙大多在胡诌) 但是确实如此啊. 看数据范围想状压.(我额嗯嗯想到暴力?) 然后设出一个可爱的$dp$式(主语当然不是我,是出题人大佬) $f_ ...

  8. C#里的应用程序域AppDomain

    首先,描述一下AppDomain是什么:当一个程序集被执行时,系统就会自动为其创建一个AppDomain,每一个AppDomain属于某个进程,一个进程内可以有多个AppDomain:每个AppDom ...

  9. WebForm与MVC模式优缺点(转)

    Asp.net Web开发方式,分为两种: 1. WebForm开发 2. Asp.Net MVC开发 MVC是微软对外公布的第一个开源的表示层框架,MVC目的不是取代WebForm开发,只是web开 ...

  10. 洛谷P1456Monkey King

    洛谷P1456 Monkey King 题目描述 Once in a forest, there lived N aggressive monkeys. At the beginning, they ...