线段树【专辑@AbandonZHANG】

(从原cnblog博客搬来的,代码都删掉了,源地址:http://www.cnblogs.com/AbandonZHANG/archive/2012/09/15/2631928.html) ♥单点更新:最最基础的线段树,只更新叶子节点,然后把信息用PointUpdate(int n, int V) || PointAdd(int n, int V)函数更新上来。   HDU 1166 敌兵布阵 (线段树入入入门题 || 第一个线段树程序) POJ 2828 Buy Tickets ★(好题,逆推思路) 问题抽象求第K小点 思路:分析发现, 第i个人对他后面的人的位置都有可能有影响,但对他前面的人的位置一定没有影响。所以,我们可倒着插队。从后向前,先操作第n个人,当操作第i个人时,我们将他插在第pos[i]+1个空处。因为此时区间内的空处,是正着插队时前面奶牛所占的位置。对于求第K点,线段树时跑不过SBT。(嗯~这题可以SBT的......感觉线段树在这儿就是起了一个维护前缀和还有二分搜索的作用......) ♠HDU 4302 Holedox Eating ★(2012 Multi-University Training Contest 1) 问题抽象求特殊区间最大点、最小点思路:每次左边右边寻找距离当前位置最近的点,则用线段树求[0,now]区间最大数,求[now,l]区间最小的数维护即可。 ♠POJ 3264 Balanced Lineup (离线RMQ,更新都不用,轻松1Y,简单题=。=) ♠HDU 4288 Coder ★(2012 ACM/ICPC Asia Regional Chengdu Online) 问题抽象分组线段树求和思路:离线(离散化+排序)维护5颗线段树。sum[rt][5]的每棵树表示区间的数以该区间左端为起点mod 5的余数,cnt[rt]表示区间数的数目。一开始不知道怎么动态地维护插入、删除数据的位置的模5的余数,比如一开始插入1、3、5,5是要求的,但要是再插入个2变成1、2、3、5,那么就变成3了。。。这个让我想了好久,后来经过一些提示终于想到了思路:每个叶节点的值都附在sum[rt][0]里,即上面说的,sum[rt][i]表示以该区间左端点为起点mod 5的余数。那么在向上统计汇总时怎么转化呢? 答案是:sum[结点][i]=sum[左儿子][i]+sum[右儿子][((i+5)-cnt[左儿子]%5)%5]。 什么意思呢?从sum的意义出发,左儿子的区间左端点和父节点是一样的,所以他们的余数等价;然而需要把右儿子的左端点与父节点等价起来。设父区间左端点为a,则右儿子区间左端点即为a+cnt[左儿子]。若右儿子(pos-a)%5==i,则把它放到父区间(pos-a-cnt[])%5== i-cnt[]%5== (保证大于等于0小于5) ((i+5)-cnt[]%5)%5。 另外要注意这里离散化的方法~~~lower_bound()函数可以很方便的找到数在原数组中的位置。(或者自己写一个二分也可以。。。) ♠HDU 4417 Super Mario ★(2012 ACM/ICPC Asia Regional Hangzhou Online) 问题抽象询问区间内比一个数的权值小的数的个数 思路:将所有的询问离线读入之后,按H从小到大排序。对于所有的节点也按从小到大排序,然后根据查询的H,将比H小的点加入到线段树,然后就是一个区间和。 另一个思路:二分+区间第k大(划分树、函数式线段树……) ♠HDU 4366 Successor ★(2012 Multi-University Training Contest 7) 问题抽象询问区间内比一个数的first权值大的数中second权值最大的数 思路:先一遍dfs将树形结构转为线性结构,转化后的每个点对应一个区间,区间内是它所有的子孙。然后问题就转化成了求区间内比一个数的权值大的数中权值最大的数。和上面那题很像了。将所有询问离线读入,按能力值从大到小排序。对于节点也按从大到小排序,然后根据查询点的能力值,将比它能力值大的点都插进线段树中,这样保证了线段树中存的都是满足条件的数(能力比上司高),再在对应区间内找个忠诚值最高的就好了(线段树中维护个max值什么的...)。 悲剧的是我在dfs爆栈了=.=。。。其实我觉得这个栈溢出的挺正常,毕竟50000个点压进栈什么的...=.=,可别人的为什么不会呀T_T,姿势也差不多呢呀,HDU 4358那道题为什么没爆呀=.=。。。算了,先放着吧,有时间了手动模拟栈再重新做一遍。。。   ♥区间更新:(通常这对初学者来说是一道坎)需要用到延迟标记(懒惰标记),简单来说就是每次更新的时候不要更新到底,用延迟标记使得更新延迟到下次需要更新or询问到的时候。(这个阶段zkw线段树的精髓吾尚不能参透T_T......所以就结合用懒惰标记了=。=......) 注意:由于是对每个区间的值进行修改,如果我们每次修改都修改到叶子节点,那么修改复杂度就降到了O(n),必然导致TLE,所以要用懒惰标记。{ 注意这里默认的总区间是[1..M]不是HH大牛的[1..n],用错了区间和数组下标会对不上(因为zkw式建树和HH大牛的建树方式不同,zkw式在BuildTree()时建的就是[1,M]的满二叉树,这样建树的好处是下标与区间对应的很工整(详见《统计的力量》),查找会很方便。而HH牛的线段树开的是[1,n]的树)。。。而且要注意初始化时是从M赋值还是M+1,如果从M开始赋值(第0个叶节点开始表示区间1),则总区间为[1..M];如果从M+1开始赋值(用第1个叶节点开始表示区间1->前面还有第0个叶节点),则总区间为[0..M-1]。为了方便且易查错,我的程序就统一从M开始赋值,总区间定为[1,M] }   懒惰标记就是:如果当前的区间被要更新的区间所覆盖,直接就改变当前结点的信息,不用再往下了,所以,每次在更新的时候,只更新一层,把当前结点的信息往下传递一层,以供下一步使用,如果下一步完成了更新,更新也就结束了,没有完成,继续往下传递一层。。。。 ♠POJ 3468 A Simple Problem with Integers (区域修改->懒惰标记 入门题) ♠HDU 4031 Attack ★(2011 ACM/ICPC Asia Regional Chengdu Site —— Online Contest) 思路:如果没有防守间隔的限制,那么就是一道裸的线段树区间增减、单点查询问题。防守间隔的限制貌似阻碍了我们直接对区间修改,因为区间中不同点的防守状态不尽相同,就要个个针对,如果这样貌似的话就又退化成了N次单点更新---显然不可取。 一个重要的思路就是分而治之---把攻击和防守分开考虑。截至当前时间,一个点被成功攻击的次数 = 总攻击数 - 这段时间这个点防御的次数。(具体怎么求防守次数,看了代码就会很好理解的~~~)(因为模板的初始化sum add的范围(1, 2<<N)没弄对WA一次T_T……又返回去把前面的模板修正了下……) ♠HDU 4027 Can you answer these queries? ★(2011 ACM/ICPC Asia Regional Shanghai Site —— Online Contest) 思路:这道题的难点在于处理平方根时区间不好处理。一开始把思路集中在和的平方根和平方根的和的关系上,果然还是思维不够宽阔呐~这道题如果能想到一个I64整数也最多开平方8次就成1的话就简单了。在区间上加一个域bol[]判断该区间的数是不是都是1了,如果是则不用修改了。这样下来没个点最多被修改8次,不会超时。 ♠POJ 2528 Mayor's posters (坐标离散化->数组hash)   问题抽象线段插入问题。(通常需要把坐标离散化成“线段”)HDU 4325 Flowers (2012 Multi-University Training Contest 3)(坐标离散化->map hash) ♠HDU 4267 A Simple Problem with Integers ★(2012 ACM/ICPC Asia Regional Changchun Online)(分组线段树) 做网络赛的时候没想到(经验太少了T T……),比完赛请教了一个大牛才学会这种处理方法。 思路: 比较容易往线段树上想的。但是由于更新的是一些离散的点,比较麻烦。可以考虑这些点的共性,总是隔几个,更新一个,而且注意这个条件“(i-a)%k==0”可以转化为“i%k == a%k ==mod ”,那么我们就可以把区间内的数关于k的余数分组。这样每次更新的都是其中的一组,而且是连续的。由于 K比较小,这是本题的突破口,那么关于k的余数情况,最多只有55种。即如果k=1,则分为1组,k=2分为2组……(如果直接开10*10的数组会MLE = =……今年卡内存卡的那叫个……)最后查询也一样,枚举点所在的所有分组上的和都加起来就行了。 ♠HDU 3954 Level up ★(2011 Alibaba Programming Contest ---by NotOnlySuccess)(分组线段树) HH神出的一道线段树神题~ 思路:题意很简单,成段更新,成段询问,但是更新却和一般的线段树大不一样,每个点虽然接收到相同的信息,但是由于本身不同,最终得到的值也是不同的.用一般的延迟操作就搞不定了. 突破点在K,范围很小,只有10,可以考虑每次有人升级的时候,就递归的找下去,将这个人进行升级操作.由于找到某个人只需要logn的复杂度,每个人最多升k次,所以n个人的复杂度是O(nklogn)。用了两个辅助数组add[maxn]和MAX[maxk][maxn],add用于记录延迟标记,MAX[k]表示该区间等级为k的最大经验值.初始化add,MAX[1]为0,其他为-1,表示无人在这个等级.当MAX[k]的值大于等于Needk时,就对这个区间进行升级操作,和线段树操作一样递归的将这个区间能升级的人全部升级. 单次操作可能会是nlogn(每个人都升级),但是平均下来还是只有nklogn. ♠HDU 4358 Boring Counting ★★(2012 Multi-University Training Contest 6) 问题抽象区间内恰好出现K次的数的个数。 思路Here~ (内容有点儿多,单独放一处了) ♠CodeForces Round #149 E XOR on Segment ★(区间异或&&分组线段树) 题目大意:序列a有n个数。实现两个操作:①求[l,r]区间和  ②对某个区间[l,r]所有数异或一个数x。 思路:比较容易想到是用线段树,因为每个数都小于10^6,所以把每一位拆成20位储存,这样就用20颗线段树。那么问题就转化为区间01异或问题:0异或任何数不变,不用修改,1异或一个数x结果是1-x。。(详细理解还是看代码吧。。   ♥区间合并:这类题目会询问区间中满足条件的连续最长区间,所以PushUp的时候需要对左右儿子的区间进行合并,所以一般情况下除了设置本区间最长连续mmax外,还需要设置从区间左端开始最长连续lmax和从区间右端开始最长连续rmax,以便于和左右两边区间合并。POJ 3667 Hotel (区间合并入门) 题目大意区间内最长连续房间数。 问题出来了,对于二叉树,或许某子树根的左孩子的右边跟右孩子的左边连续着呢,怎么办? 于是,我们开出三个数组 lsum[] rsum[] 和 sum[]。对于区间 [L, R],lsum[rt]表示以 L为开头的最长连续房间数,rsum[rt]表示以R为结尾的最长连续房间数,sum[]表示[L,R]内的最长连续房间。 继续分析:当 lsum[rt<<1]等于左孩子区间总长度时,lsum[rt<<1]和lsum[rt<<1|1] (即左孩子的lsum和右孩子的lsum)是相连的; 同理得, 当 rsum[rt<<1|1]等于右孩子总长度时,rsum[rt<<1|1]和rsum[rt<<1](即右孩子的rsum和左孩子的rsum)是相连的。 而对于一个 sum[rt]= max{ rsum[rt<<1]+lsum[rt<<1|1], sum[rt<<1], sum[rt<<1|1 }。 ♠HDU 4339 Query (2012 Multi-University Training Contest 4) 问题抽象最长连续公共子串
62 Abandon.burning 2000MS 10444K 2333B C++ 2012-10-01 08:42:19
HDU 4351 Digital root ★★(2012 Multi-University Training Contest 6) (线段树值得研究的一道好题)
区间合并->二次合并(pushup)->更新时要合并左右子区间,询问时要合并左右询问子区间(这是不一样的,比如在[1,8]中询问[1,6],那么左右子区间分别是[1,4],[5,8],而左右询问子区间是[1,4],[5,6])。 预备知识一个数的数字根就是这个数mod 9的余数 (余数为0则取9,当然如果这个数本身是0那就是0了……) 思路:因为题目中所查询的区间,是所有的子区间的结果的并。所以区间合并就很必要了。 每个结点,记录这个区间的和的数根, 记录本区间内从左端点为端点往右的连续区间出现的数根,从右端点为端点往左的连续区间出现的数根,以及整个区间能出现的数根。然后合并什么的。。。 但显然还没有结束,不然我也不会把他视作好题了。。。这道题用线段树会卡时限!需要各种优化------主要是算数字根的打表优化。 做了一天吧T_T。。。上午想好了思路,然后一下午+晚上完成了一个加上打表、输入输出优化还是TLE的代码。。。T_T。。。然后就捉鸡了。。。然后第二天上午终于找到哪儿超时了(我说怎么加了打表优化和输入输出优化还超T_T),竟然是开始回答询问时的那四个memset T_T,然后恍然大悟。。。我竟然sb的给ans也建了颗线段树!活该作死啊。。。T_T。。。 然后把ans改为节点,加上打表优化、输入输出外挂后终于A了T_T  (速度还不是很快啊。。。网上找了个程序1100MS呜呜。。。):
6851969 2012-10-02 15:18:54 Accepted 4351 1546MS 32140K 6320 B G++ Abandon.burning
  扫描线:这类题目需要将一些操作排序,然后从左到右用一根扫描线(当然是在我们脑子里)扫过去。最典型的就是矩形面积并,周长并等题
HDU 1542 Atlantis (矩形面积并入门题) 问题抽象矩形面积并 其他入门题:POJ 1151 (就是这道题,不过POJ的数据比较弱...)、POJ 1389POJ 3277(所有矩形底边都在一条水平线上) 思路:浮点数先要离散化; 对于一个矩形(x1, y1, x2, y2),只取上边和下边,结构体ss[] 记录一条边的左右点坐标和距坐标轴的高度,再拿一个变量 ss[].s记录这条边是上边还是下边,下边记录为1,上边记录为-1。按高度排序,对横轴建树,从低到高扫描一遍。若下边,则加入线段树;若上边,则从线段树上去掉。用cnt表示该区间下边比上边多几条线段,sum代表该区间内被覆盖的长度总和。 这里线段树的一个结点并非是线段的一个端点,而是该端点和下一个端点间的线段,所以题目中r+1,r-1的地方要好好的琢磨一下。 可以去看看nocLyt的图解:http://blog.sina.com.cn/s/blog_5e8c2b4e01011j9e.html 问题抽象矩形面积并 贴矩形海报,每次贴前都要在海报中剪个小的矩形洞,然后求面积并。思路很简单,把单个海报剪掉一个洞后看成四个小海报就行了,分成四个小矩形时要注意下边界处理问题,然后直接套HDU 1542的模板1Y了 =.=。。。
  ♥二维线段树:此类线段树没什么意思,就是代码量加倍了而已,运用范围也没一维线段树广,会写就行了
二维线段树需要有两个维度,所以实现它的最基本思想就是树中套树。假设有一个矩形横坐标范围1—n,纵坐标范围1—m。我们可以以横坐标为一个维度,建立一棵线段树,假设为tree1,在这棵树的每个节点中以纵坐标建立一棵线段树,设为tree2,假设我们在tree1所处在的节点的的横坐标范围为l,r,那么该节点表示的矩形范围为横坐标为l—r,纵坐标范围为1—m。若我们正处在该节点中tree2的某个节点,该节点的纵坐标范围为d—u,那么tree2中的这个节点所代表的矩形范围,横坐标l—r,纵坐标d—u。所以我们看到,树套树&&二维线段树只要理解其思想就会发现其实并不难。 ♠HDU 1823 Luck and Love (二维线段树入门) 因为有两个限制范围,所以需要二维的线段树。也没什么好说的地方。 需要注意几个问题:1.初始化sum = -1,不然所有的返回结果都是>=0的数,查找不到的情况不好判断。 2.坐标左小右大(因为这个白白贡献几次WA擦。。。) 3.有可能存在同一身高、活泼度的人,所以更新时不是直接赋值而是选个缘分值最大的。
 
(未完待续...)
举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG
原文地址:https://www.cnblogs.com/AbandonZHANG/p/4114171.html