树状数组、线段树题目集

一、《树状数组学习系列2 之 OJ题目大汇总——czyuan原创》czyuan

万恶的百毒几年前把百毒空间关!掉!了!幸亏原帖在Web Achieve中还有:https://web.archive.org/web/20091013230958/http://hi.baidu.com:80/czyuan_acm/blog/item/af6fe8a9177f7ef51e17a2ea.html

简单:
POJ 2299 Ultra-QuickSort
http://acm.pku.edu.cn/JudgeOnline/problem?id=2299
求逆序对的数量。可以用经典的归并排序做,也是基本的树状数组题目。

POJ 2481 Cows
http://acm.pku.edu.cn/JudgeOnline/problem?id=2481
将E从大到小排序,如果E相等按S排序,然后求逆序对的数量。

POJ 3067 Japan
http://acm.pku.edu.cn/JudgeOnline/problem?id=3067
先按第一个坐标排序从大到小排序,如果相等按第二个坐标从大到小排序,然后求逆序对的数量。

POJ 2352 Stars
http://acm.pku.edu.cn/JudgeOnline/problem?id=2352
题目意思就是求每个星星左下方的星星的个数,由于y轴已经排序好了,我们可以直接用按x轴建立一维树状数组,然后求相当于它前面比它小的个数,模板直接一套就搞定了~~

HOJ 2275 Number sequence
http://acm.hit.edu.cn/hoj/problem/view?id=2275
两个一维树状数组,分别记录在它左边比它小的和在它右边比它大的即可~~

HOJ 1867 经理的烦恼
http://acm.hit.edu.cn/hoj/problem/view?id=1867
先筛法求素数,然后如果从非素数改变成素数就Update(x, 1),如果从素数改变成非素数就Update(x, -1)即可。

SGU 180 Inversions
http://acm.sgu.ru/problem.php?contest=0&problem=180
经典树状数组 + 离散化。注意结果要用long long~~

POJ 1195 Mobile phones
http://acm.pku.edu.cn/JudgeOnline/problem?id=1195
二维的树状数组,直接把Update()和Getsum()改为二维即可。
如Update()函数改为:

void Update(int x, int y, int d)
{ // 注意:当i = 0,0 + Lowbit(0) = 0,会造成死循环!
  int i, j;
  for (i = x; i < maxn; i += Lowbit(i)) // 注意这里是maxn,是tree[]的大小.
    for (j = y; j < maxn; j += Lowbit(j))
      tree[i][j] += d;
}

SPOJ 1029 Matrix Summation
https://www.spoj.pl/problems/MATSUM/
基本的二维树状数组。

中等:

POJ 2155 Matrix
http://poj.org/problem?id=2155
经典,楼教主出的题目~~原题是二维的,我们先化简为一维的讨论。

题目要求有两种操作:

  1. 改变某个(a, b)内的所有。
  2. 求某个点(a)的值。

这题网上的解题版本几乎都是直接抄百度百科中的树状数组的讲解http://baike.baidu.com/view/1420784.htm(这个版本我理解了好长时间...)

以下是个人的理解:
很明显,这题与树状数组的操作正好相反。我们可以想想树状数组中的两个函数Update()修改某个点的值(准确说是某个路径,递增或递减),Getsum()求和区间(1, x)内的点的值。我们上面已经分析了,这两个函数本质上是相同的,可以互相调换的。换句话说就是修改某点x和求和某个区间(1, x)是可以互相调换的,即我们可以用Getsum(x, c)修改(1, x)这个区间内的点的值,而用Update(x)来求该点的值。而两个函数的写法与原来完全相同(或者说就函数名调换了下),仅仅是思想变化了下。(这里很难理解就是因为只是思想变化,而程序与原来基本没变)
那么回到原题,我们要使区间(a, b)内的点 + c,只需要使区间(1, b)内的点+c,而区间(1, a-1)内的点-c即可。如果是二维的,修改矩阵\((x1, y1)\)\((x2, y2)\),即\((x2, y2)+c\), \((x1-1,y2)-c\), \((x2, y1-1)-c\), \((x1-1,y1-1)+c\)即可。
通过以上的分析,我们可以发现其实Update()和Getsum()这两个函数是相同的,我们可以用Up()和Down()来代替它们。Up()为操作x递增的路径,Down()为操作x递减的路径。
Up()和Down() 有四种组合:

  1. Up()表示修改单点的值,Down()表示求区间和。
  2. Down()表示修改单点的值,Up()表示求区间和。
  3. Up()表示修改区间,Down()表示求单点的值。
  4. Down()表示修改区间,Up()表示求单点的值。

1和2根据求比它大还是比它小来选择,而3和4种适用条件则相同,用其中一种即可。

POJ 3321 Apple Tree
http://poj.org/problem?id=3321
这题的难点不在于树状数组,而是如果将整棵树映射到数组中。我们可以用DFS()改时间戳的方法,用begin[i]表示以i为根的子树遍历的第一个点,end[i]表示以i为根的子树遍历的最后一个点。
比如数据为:
5
1 2
2 5
2 4
1 3
那么begin[] = {1, 2, 5, 4, 3}, end[] = {5, 4, 5, 4, 3},下标从1开始。
对于每个点都对应一个区间(begin[i], end[i]),如果要改变点a的状态,只要Update(begin[a]),要求该子树的苹果树,即Getsum(begin[a]) - Getsum(end[a] + 1)。这里求和是求x递增的路径的和

POJ 1990 MooFest
http://poj.org/problem?id=1990
这题的难点是要用两个一维的树状数组,分别记录在它前面横坐标比它小的牛的个数,和在它前面横坐标比它小的牛的横坐标之和。
按音量排个序,那么式子为:
\(ans += 1LL * cow_i.volumn * (count * x - pre + total - pre - (i - count) * x)\)
cow[i].volumn为该牛的能够听到的音量。
count为在第i只牛前面横坐标比它小的牛的个数。
pre为在第i只牛前面横坐标比它小的牛的横坐标之和。
total 表示前i - 1个点的x坐标之和。
分为横坐标比它小和横坐标比它大的两部分计算即可。

HDU 3015 Disharmony Trees
http://acm.hdu.edu.cn/showproblem.php?pid=3015
跟上题方法相同,只要按它的要求离散化后,按高度降序排序,套用上题二个树状数组的方法即可。

HOJ 2430 Counting the algorithms
http://acm.hit.edu.cn/hoj/problem/view?id=2430
这题其实是个贪心,从左往右或者从右往左,找与它相同的删去即可。先扫描一遍记录第一次出现和第二次出现的位置,然后我们从右到佐,每删去一对,只需要更改左边的位置的树状数组即可,因为右边的不会再用到了。

TJU 3243 Blocked Road
http://acm.tju.edu.cn/toj/showp3243.html
这题主要在于如果判断是否连通,我们可以先用j = Getsum(b) – Getsum(a – 1),如果j等于(b – a)或者Getsum(n) – j等于(n – (b – a)),那么点a, b联通。

SPOJ 227 Ordering the Soldiers
http://www.spoj.pl/problems/ORDERS/
这题与正常的树状数组题目正好想反,给定数组b[i]表示i前面比a[i]小的点的个数,求a[]数组。
我们可以先想想朴素的做法,比如b[] = {0, 1, 2, 0, 1},我们用数组c[i]表示还存在的小于等于i的个数,一开始c[] = {1, 2, 3, 4, 5},下标从1开始。
我们从右向左扫描b[]数组,b[5] = 1,说明该点的数是剩下的数中第4大的,也就是小于等于它的有4个,即我们要找最小的j符合c[j] = 4(这里可以想想为什么是最小的,不是最大的,挺好理解的),而c[]是有序的,所以可以用二分来找j,复杂度为O(logn),但现在问题是每次更新c[]要O(n)的复杂度,这里我们就想到树状数组,c[i]表示还存在的小于等于i的个数,这不正好是树状数组的看家本领吗~~所以处理每个位置的复杂度为O(logn * logn),总的复杂度为O(n * logn * logn)。

hdu 2852 KiKi's K-Number
http://acm.hdu.edu.cn/showproblem.php?pid=2852
这题与上面那题类似,只是要求比a大的第k大的数,那我们用Getsum(a)求出小于等于a的个数,那么就是要我们求第k + Getsum(a)大的数,而删除操作只要判断Getsum(a) – Getsum(a - 1)是否为0,为0则说明a不存在。

难题:

POJ 2464 Brownie Points II
http://poj.org/problem?id=2464

这道题用二分也可以做的,这里介绍下树状数组的做法。首先有n个点,过每个点可以做x,y轴,把平面切成BL, TL, TR, BR四个部分,我们现在的问题是如果快速的计算这四个部分的点的个数。
这样我们可以先预处理,先按y坐标排序,求出每个点正左方和正右方的点的个数LeftPoint[], RightPoint[],复杂度为O(n),同样我们再以x坐标排序,求出每个点正上方和正下方点的个数UpPoint[], DownPoint[]。还要求出比点i y坐标大的点的个数 LageY[]。注意:这里要进行下标映射,因为两次排序点的下标是不相同的。
然后按x坐标从小到大排序,x坐标相等则y坐标从小到大排序。我们可以把y坐标放在一个树状数组中。
对于第i个点,求出Getsum(y[i])即为BL的个数,然后Update(y[i])。由于现在是第i点,说明前面有i – 1个点, 那么
TL = i - 1 - LeftPoint[i]- BL;
TR = LargeY[i] - TL – UpPoint[i] ;
BR = n - BL - TL - TR - LeftPoint[i] - RightPont[i] - UpPoint[i] – DownPont[i] - 1;
这样我们就求出四个部分的点的个数,然后判断有没有当前解优,有的话就更新即可~

UVA 11610 Reverse Prime
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2657
一道很综合的树状数组题,用到了树状数组中的很多知识点,包括离散化,二分查找等。

  1. 先按题目要求筛法素数,找到所有的Reverse Prime。
  2. 将这些Reverse Prime离散化,只有78500个左右。树状数组中tree[i]记录比i小的点的个数。
    当执行q a操作时,二分查找最小的j, 使得Getsum(j) 等于 ++a(因为a可能为0,所以统一加一)。这步与上面SPOJ 227 Ordering the Soldiers 类似。
  3. 当执行d a操作时,先找到a离散化后的值b,然后Update(b, -1)即可。
    按这样做后,运行时间为: 0.3s多,感觉很诧异,因为都是0.1s以下的。这里特别感谢liuzhe大牛的指点,其实题目中的Reverse Prime是由10^6以下的素数倒置得到的,那么得到的要求是7位,最后一位一定是0,我们可以对每个除以10处理,那么数的范围就减小了10倍,速度就提高了不少。
    最后自己又加了点优化,跑了0.056s,排在第3名~~
    3 7313367 czyuan 0.056 C++

czyuan原创,转载请注明出处。

二、《线段树题目20道汇总+简要算法+分类+难度》 黑梦楠

转载已补加标点,与上文重复的简单题目已匿,所以少了3题。相似内容写在一起了。

难度系数分为从1到5 (只对初学者有用,对大牛来讲这些题的难度系数都是0……)

http://poj.org/problem?id=3468
A Simple Problem with Integers
求区间和。(转载注:可用树状数组)
难度系数 *

http://poj.org/problem?id=3264
Balanced Lineup
RMQ。
难度系数 **

http://poj.org/problem?id=2823
Sliding Window
RMQ,经典的问题。
难度系数 **

http://poj.org/problem?id=2777
Count Color
线段染色问题,很好做,解题报告也一大堆,但希望自己敲敲。
难度系数 **

http://poj.org/problem?id=2528
Mayor's posters
线段染色问题。我想说的是注意离散化的方法,不要以为AC了的程序就是完全正确,详情可以看这题的discuss。
难度系数 **

http://poj.org/problem?id=3277
City Horizon 线段树求和、线段、插入等
基础题。不过USACO的标程挺NB的,用set+pair构造线段树,有时间一定学学啊。其实这就是说红黑树添加一个线段域也就成了线段树了……《算法导论》有讲解。
难度系数 **

http://poj.org/problem?id=1151
Atlantis
http://poj.org/problem?id=1389(转载注:两题一样)
Area of Simple Polygons 扫描线+离散化+线段树
这是经典的扫描线求矩形面积交,很好过,没什么陷阱。如果头一次接触扫描线,那么难度系数大概算3吧,如果熟练掌握扫描线,难度系数为1。
难度系数 ***

http://poj.org/problem?id=2761
Feed the dogs
据说wind养了100000只狗题目要求和2104基本一样,但是2104的经典算法在这里不适用。一定要注意"Hence any feeding inteval will not contain another completely, though the intervals may intersect with each other. "这句话为什么自己要仔细琢磨啊。做这题至少要会用线段树求第k小数。
难度系数 ***

http://poj.org/problem?id=2828
Buy Tickets
朱泽园出的题,线段树计数,从队伍后往前做。
难度系数 ***

http://poj.org/problem?id=1823
Hotel
POJ3667的姊妹篇。不要看AC率不高,但是比3667容易些吧。线段树线段的插入删除,求线段树中最长的线段长度。不错的题目。
难度系数 ***

http://poj.org/problem?id=2886
Who Gets the Most Candies?
NB经典题啊!约瑟夫环的升级版本,绝对要掌握的题目,用线段树解约瑟夫环问题!网络预赛就有个这样子的题,不过我当时不会唉……这题比当时网络预赛难,容易出错。
难度系数 ****

http://poj.org/problem?id=1177
Picture 扫描线+线段树
扫描线求矩形周长的并,比求面积并难。线段树中的域要多考虑几个部分,需要掌握:维护线段树、存储线段的段数与长度和。经典中的经典题目。
难度系数 ****

http://poj.org/problem?id=2104
K-th Number 线段树维护归并排序树+三次二分查找
别以为这题AC率高就容易,多数人没用这算法而是水过去的,为了练习线段树还是好好做吧…… 三次二分挺容易出错的。
难度系数 *****

http://poj.org/problem?id=2482
Stars in Your Window 扫描线+离散化+线段树
刘汝佳黑书中介绍过算法,不过我觉得不是很好看懂。
题目规定的矩形框高度为h。比如,遇到一个星星S位置是\((x_i,y_i)\),亮度为\(b_i\),那么线段树区间\([y_i,y_i+h)\)增加\(b_i\)。线段树的每个区间结点保存了该区间内的最大值。可以从贡献的角度来理解,星星S对区间\([y_i,y_i+h)\)的贡献度为\(b_i\)。扫描线在x轴方向标记进出的线段和求矩形面积并,进的话cover++,出的话cover--。
经典中的经典题,题目描述感人。
难度系数 *****

http://poj.org/problem?id=3667
Hotel
NB题中的NB题!真正理解了这题就真正理解了线段树!解题报告有很多,这题涉及了线段合并、线段插入、删除、求线段树上最大连续线段长度、线段求和等,一定要做的题目。
难度系数 *****

http://poj.org/problem?id=3695
Rectangles
说实话线段树求矩形面积并可以用容斥原理……这题挺猥琐的,算法没难度,不过如果搞不好的话非常容易超时。下面是我在discuss中留的言:

  1. TLE的话应该是没离散化。这题必须离散化,原以为最长1000的线段可以不离散化,可是最多有20个矩形,那最多就有40个线段。100000*log40和100000*log1000,时间肯定是不一样……
  2. 只建立一次线段树。不要问一次建一次,因为加入的线段过后肯定会被删除。
  3. 最好只开始的时候对线段排次序,然后开个mark[]数组,记录哪几个矩形的线段是此次询问要选的,不要每次询问都对线段排序。
  4. 别用G++交……G++比C++平均慢了500MS,这题就卡了那么点时间。
  5. 前边的优化如果都做了的话应该就过了。

上边是POJ的20道线段树题目,欢迎大家分享与补充,指正错误!转帖请注明出处。数据结构是门艺术,算法也是门艺术,线段树是艺术中的艺术!

posted @ 2017-08-17 14:56  Planet6174  阅读(378)  评论(0编辑  收藏  举报