[ACM] 线段树经典题

POJ 2528 Mayor’s posters

m次区间染色,每次染色使用的颜色都不同,颜色可以覆盖,最后询问颜色数量,区间大小n为1e7,m为1e4。
做法一:
使用线段树,区间染色时间复杂度O(logn),由于只询问一次颜色,可以O(n)遍历线段树暴力查询颜色数量。由于这道题有多组数据,O(n)的复杂度可能过不去,因此需要事先离散化一下,复杂度降为O(mlogm),此题离散化有陷阱,具体参见洛谷用户xzyxzy的题解。
做法二:
做法一代码较为复杂,考虑不使用离散化,发现可以倒序做。首先最后一次区间染色的颜色一定不会被覆盖,然后考虑之前的颜色,某个颜色若不想被完全覆盖,其之后的染色区间就不能完全覆盖当前的染色区间。因此,可以使用一个线段树,长度为n,一开始所有叶节点权值都为0,支持两种操作,操作一是将一段区间全部赋值为1,操作二是查询一段区间是否有0。从后往前遍历所有染色区间,对每个区间先进行操作二,如果有0则答案加一,然后再对这段区间做操作一。该算法复杂度O(mlogn),比做法一慢,但一样能过,并且代码简单。

HDU 1540 Tunnel Warfare

长度为n的区间,一开始全为1,m次操作,操作一是把某点从1变成0或从0变成1,操作二是计算某点附近的连续1的个数。n,m为5e4。
做法一:
建立一个线段树,一个区间保存两个值,第一个是从区间左端点开始,在该区间内有多少个连续的1,第二个是以区间右端点结尾,在该区间内有多少个连续的1。该线段树支持两种操作,操作一是修改某个点保存的值,操作二是查询某个点附近的连续1的个数,查询的时候需要分三种情况讨论:连续的1全部在区间左半区,连续的1横跨区间左右半区,连续的1全部在区间右半区。建好线段树,这个题基本上就完成了,时间复杂度O(mlogn)。
做法二:
计算某点附近连续1的个数,可以通过找到该点的前驱零和后继零来算。寻找前驱和后继操作让我们想到了平衡树。把某点从1变成0,就把该点的位置插入平衡树。把某点从0变成1,就在平衡树中删除该店的位置。计算某点附近的连续1的个数,就在平衡树中找该点的位置的前驱和后继,然后相减就可求出,时间复杂度O(mlogn)。平衡树可以使用STL自带的set,或者手写一个权值树状数组,不推荐手写treap,splay,AVL,SBT或RBT之类的平衡树,因为代码过于复杂。

POJ 2828 Buy Tickets

n个人排队,从第一个人开始,每个人选择当前队伍中的一个位置,插入到该位置后面,最后按顺序输出整个队伍所有人的序号。n为2e5。
做法一:
某个人若是选择了i位置插入,那么在该人之前,有且仅有i个人站在该人之前。从最后一个人开始,逆序考虑问题。将某个人插入队伍i位置后面时,其插入位置前必然有且仅有i个空位,因为我们是逆序考虑问题,之前的人都还没有插入队伍。因此使用一个平衡树,存放所有空位,初始时将所有位置都插入平衡树,然后逆序考虑问题,将某个人插入到队伍i位置之后时,找到平衡树的第 i + 1 个数,这个数就是该人的最终位置,同时将查询到的数从平衡树中删除,因为该位置不再是空位。该做法时间复杂度O(nlogn)。平衡树使用手写的权值树状数组,STL的set由于没有找第k大的功能,因此不适用。
做法二:
先考虑队首的人是谁,显然队首的那个人,选择的插入位置一定是0,那如果有多个人选择了0位置插入,谁是队首呢?答案显然是最后一个选择了0位置插入的人。我们找到队首以后,在该人之前插入队伍的人,如果选择了i位置插入,那么其实际位置至少是 i + 1,因为他们都被队首的人往后挤了一个位置,所以我们需要区间修改。修改完之后,再考虑队伍第二个人是谁,方法和找队首的方法一样,然后再考虑第三个人……第n个人。因此我们只需要一个线段树,支持两种操作,操作一是区间修改,操作二是查找当前的最小值,如果有多个最小值,返回最靠后的一个。该做法的时间复杂度为O(nlogn)。此题有多组数据,输入量较大,建议使用快读。

POJ 1177 Picture

求n个矩形的周长并,n为5e3,周长范围[-1e5, 1e5]。
将每个矩形分成两条横线和两条竖线,分别处理所有的横线和竖线。以竖线为例,先将所有竖线按横坐标排序,然后遍历所有竖线。需要注意的是,一个矩形会产生两条竖线,它们横坐标不同,纵坐标范围相同。遍历所有竖线时,如果当前竖线是原矩形中横坐标较小的那一条,将纵坐标范围内的所有点点权加一;如果是横坐标较大的那一条,将纵坐标范围内所有点点权减一。同时在遍历所有竖线时,每遍历到一条竖线,就查询一下当前线段树中点权大于0的点的个数,得到的查询值减去上一次的查询值,然后取绝对值,就是这条竖线处对周长的贡献。(这里不是很好理解,建议自己动手模拟一下)
那么问题就传化成为了两种操作,第一种是区间加(减)一个数,第二种是查询区间中点权大于0的点的个数。一般区间修改区间查询的问题,都可以用带lazy标记的线段树实现,但带lazy标记的线段树在这个问题上不适用,原因在于,区间减去一个数之后,没法在O(1)的时间内更新区间点权大于0的点的个数。(想想其它问题中带lazy标记的线段树,区间修改时会给整个区间打上lazy标记,然后在O(1)时间内更改整个区间维护的值)
那么是否说明这道题无法使用线段树做呢?也未必。注意这个问题的两个性质,1. 查询时查询的不是任意区间,而是每次只查询整个区间,这意味着,我们无需计算清楚每个区间中点权大于0的点的个数,只需要计算清楚整个区间中点权大于0的点的个数即可。2. 如果对某个区间减一,那么这个区间之前一定加过一,这样,整个线段树中不可能出现负数。
因此我们使用这样一个线段树,区间修改的时候,不打lazy标记,只更改被修改的区间。也就是说,后代区间并不会知道祖先区间被修改了,后代区间维护的点权大于0的点的个数,都是在不考虑祖先区间的修改情况下的。对于一个区间,如果修改后区间的值大于等于1,那么说明这个区间中所有点的点权都大于等于1,那么其点权大于0的点的个数就等于其区间长度;如果修改后区间的值等于0,那么在不考虑当前区间的祖先区间修改的情况下,想要使区间中的点点权大于0,只有可能是当前区间的后代区间被修改过并且值大于0,这个区间中点权大于0的点的个数,就等于其两个子区间中点权大于0的个数和。(虽然只是两个子区间的个数和,但是包括了所有后代区间)
这样,问题就被解决了,复杂度为nlogm,m等于2e5(周长范围)。
这道题的线段树和普通的线段树很不一样,要注意理解,如有必要可以结合代码(见附录)。学会这道题的思想以后,就可以很方便的解决矩形面积并(HDU 1542),矩形面积交(HDU 1255)等问题。

你可能感兴趣的:(算法/数据结构)