线段树的灵活运用

线段树算是区间数据结构的代表,虽然新的数据结构越来越多,可以用更高级的数据结构来回避比较困难的建模和灵活的运用,许多高级数据结构甚至就是对线段树的改进,然而这种思想对于解题来说仍然是不可或缺的——要是哪一天遇到了高级数据结构都需要灵活运用才能够解决的题目呢?因此,像线段树这样的基础必须扎实。因此,这里提供了十多道线段树可以解决的题目。

一、什么东西可以用线段树维护

只要是可合并物,即满足\(𝛼 (𝐴) 𝛼\ 𝛼 (𝐵) = 𝛼(𝐴 ∪ 𝐵)\),如\(\gcd(\ \gcd (𝐴) ,\ \gcd (𝐵)\ )= gcd(𝐴 ∪ 𝐵)\),就都可以进行合并。

甚至有一些不常见的:

  • 带修改的背包问题(用线段树维护广义背包,每个元素是一个数组,数组可合并)
    即对于线段树的每一段都开一个\(f\)数组存储作为在当前区间内选择多少重量物品时价值是多少,合并的复杂度是\(O(n)\)的,修改一次即为\(O(n\log n)\),相对于朴素快了不少。

  • NOI2017:整数 用线段树维护bitset进行高精度加减。待填。

二、线段树的经典题型和例题

1.二维偏序问题

即给出一些二维平面上的点,对于每个点求两维坐标都严格小于自身的点的个数。
考虑根据x坐标从小到大排序,将x维度转化为时间维度(常见操作),这样按排序后的顺序加入点,y坐标小于当前坐标的所有点即为所求。

三维偏序问题通常需要更复杂的数据结构,此处不展开叙述。


T1

P3431 [POI2005]AUT-The Bus

经典的二维偏序问题,考虑朴素的动态规划算法,可以从横纵坐标都小于等于当前点的所有点更新过来,根据x坐标进行排序之后,用区间数据结构(如线段树)维护区间最大值,转移时用\(O(\log n)\)的时间复杂度就可以转移,时间复杂度减小为\(O(n\log n)\),可以通过此题目。


T2

POJ1990

在数轴上有一列互不相同点 \(𝑥(1 ≤ 𝑁 ≤ 20,000 , 1 ≤ 𝑥_𝑖 ≤ 20,000)\),每个点有一个权值 \(𝑣_i\) \((1 ≤ 𝑣_𝑖 ≤ 20,000)\),对每对点 \(𝑥_𝑖\), \(𝑥_𝑗\),算出 \(|𝑥_𝑖 − 𝑥_𝑗|× max\{𝑣_𝑖, 𝑣_𝑗\}\),然后求这些结果的和。

依然是一个二维偏序问题,显然绝对值更好维护和,于是把v值从小到大当做时间维进行排序,这样就可以直接进行更新。

看到维护绝对值的题目,通常我们把绝对值拆开进行分类讨论。即转化为\(x_i-x_{now}\)\(x_{now}-x_i\)两种即可,利用线段树维护每个\(x_i\)位置上的\(v_i\)即可实现

2.合理设计懒标记进行合并

T3

HDU 4288 Coder

对一个集合进行插入与删除操作。要求询问某个时刻,集合中的元素从小到大排序之后,序号 mod 5 = 3 的元素值之和。

看到对比较小的数(大约\(≤10^6\))取模的题目,大多应该想到把对所有数取模的值全部存储下来。

由于线段树完全无法进行删除和插入节点的操作,看到这类题目大多应该想到删除就是把叶子节点置0,利用离线方法把所有可能插入进去的叶节点先全部建好。即若初始集合有\(n\)个元素,有\(q\)次插入操作,我们将线段树维护的区间长度扩大为\(n+q\)即可。

此处我们可以存储当前节点维护的某一段区间序号\(mod\ 5 =t (t<5)\)的数的个数以及当前这一段区间有多少个数。插入和删除操作按上述方法维护,预先把所有即将加入和已经加入的数排序,预留出所有数的位置即可。

注意这里的序号是相对于当前节点维护的小区间而言的。

合并两个节点时,我们只需要知道左节点的元素个数和左右两边序号\(mod\ 5 =t (t<5)\)的数的个数便可以得到大的节点序号\(mod\ 5 =t (t<5)\)的数的个数

例如左右两边均有且仅有节点有1个元素且它们序号\(mod\ 5=1\),则大节点有一个序号\(mod\ 5=1\) 一个 \(mod\ 5 =2\)的元素


于是又有一个相似的例题:

T4

P3960 [NOIP2017 提高组] 列队

你会发现本题和上面那一道题惊人的相似。

开n颗线段树维护每一行,再开一颗线段树维护最后一列的元素。

删除操作和上题类似,直接预留所有位置。

还需要支持一个操作即查询当前区间第k个元素是什么:类似于权值线段树查询第k大的数的方法,直接做即可。

最后,普通的线段树本题的空间是开不下的。但是我们会发现询问只有\(10^5\)个左右,利用动态开点,节点个数必然和询问个数的大概同阶的。


HDU 2795 BillBoard

有一个板,\(ℎ\) 行,每行 \(𝑤\) 长度的位置。每次往上面贴一张海报,长度为\(1×𝑤_𝑖\)。每次贴的
时候,需要找到最上面的可以容纳的空间,并且靠左边贴。求每次贴的位置。

利用线段树维护每一行剩下多少空间,维护其最大值即可。需要维护的操作有单点修改和查询第一个大于k的元素是什么,利用线段树或平衡树都很好实现。


T5

URAL 1019 Line Painting

给一段线段进行黑白涂色,最后问最长的一段白色线段。

维护与当前区间最左端和最右端连着的最长白色线段,以及当前区间的最长白色线段。

直接合并即可。


T6

NOI2016 区间

区间长度的选取具有单调性,可以走指针。

离散化,然后同时维护一个线段树上最大值即可。很简单。由此我们可以看出数据结构经常和其他算法搞在一起。


T7

SHOI2008 堵塞的交通

\(2 × 𝑁\)\(10^5\))的矩阵,相邻两格之间有通路。𝑀( \(10^5\) )种操作:
\(Close\ r1\ c1\ r2\ c2\):关闭 \((𝑟1, 𝑐1)\)\((𝑟2, 𝑐2)\) 之间的道路
\(Open\ r1\ c1\ r2\ c2\):打开 \((𝑟1, 𝑐1)\)\((𝑟2, 𝑐2)\) 之间的道路
\(Ask\ r1\ c1\ r2\ c2\):查询 \((𝑟1, 𝑐1)\)\((𝑟2, 𝑐2)\) 之间的连通性

考虑每一个线段最小的节点存储竖着的两个点,用线段树大力讨论维护十个量作为点之前的联通情况:

image

最后合并答案的时候还要考虑不从中间通过而从旁边绕路绕过去的情况。

3.结合位运算

T8

POJ 2777 Count Color

给线段涂颜色,最多30种颜色,10万个操作,每个操作给线段涂色,或问某一段线段有多少种颜色。

颜色很少,但是直接暴力维护还是可能会爆掉。记录下当前区间每一种颜色是否出现和是否为全为某种颜色的区间就可以。这里正好可以用一个状压数字存下来。线段太长就离线后离散化。


T9

Codeforces 482B Interesting Array
给定 𝑛, 𝑚,求满足 𝑚 个条件的 𝑛 个数,或说明不存在。
每个条件的形式是,给定 𝐿𝑖, 𝑅𝑖, 𝑄𝑖,要求
\(𝑎_{𝐿𝑖}\ and\ 𝑎_{𝐿𝑖+1}\ and\ ⋯\ and\ 𝑎_{𝑅𝑖} = Q_i\)

利用线段树维护所有的数,Q的某一位是1表示这些数当前位都应该是1,某一位是0表示这些数这一位至少有一个是0。Q的每一个数位都可以看作一个操作。于是我们可以分开0和1的操作,首先把数置为0,然后把必须位1的全部赋值上(区间修改/或运算),最后检查每一个0是否成立。只能分开检查,而分开检查也是这个题的妙处所在

4.结合动态规划

T10

Codeforces 474E Pillar
给定 \(10^5\) 个数(范围 \(10^{15}\)),求最长子序列使得相邻两个数的差大于等于 𝑑。

考虑朴素dp是\(N^2\)的,每一次要枚举所有的之前的数来转移。我们可以利用线段树优化转移过程。离散化所有的数后把数字代表的\(dp\)值用线段树维护,即线段树的一个叶子节点\([l,r](l=r)\)可以表示从以数\(l\)结尾子序列的\(dp\)值最大是多少。这样就可以\(\log n\)转移了。


T11

CF1557D Ezzat and Grid(前两天div2的比赛题)

\(f(i)\)表示前i行且以i行结尾最少需要删去多少行,这里同样需要枚举一个小于\(i\)\(j\)来转移。

同样的,离散化置为1的线段长度并利用线段树维护其对应的\(dp\)值,即线段树的一个叶子节点\([l,r](l=r)\)可以表示结尾行的第i位为1的\(dp\)值最大是多少。这样就可以\(\log n\)转移了。

5.集合字符串

T12

URAL1989 Subpalindromes
给定一个字符串(长度≤ 100,000),有 10 万个操作。
操作有两种:
1:改变某个字符。
2:判断某个子串是否构成回文串。

难道是线段树还能维护马拉车???

笑死,并不能。。。用线段树维护每次修改后正着和反着数的哈希值,如果相同即为回文串。

女少口阿

T13

Codeforces 558E A Simple Task
给定一个长度不超过 \(10^5\) 的字符串(小写英文字母),和不超过 \(5000\) 个操作。每个操作
𝐿 𝑅 𝐾 表示给区间 𝐿, 𝑅 的字符串排序,𝐾 = 1 为升序,𝐾 = 0 为降序。

最后输出最终的字符串

真恶心一个题,由于只含小写英文字母,我们可以暴力地记录下某一段区间某种字母含多少个,再维护一个排序的lazy_tag,以\(O(26)\)的复杂度\(push\_up\)\(push\_down\)即可。

6.线段树的建模

有 𝑁(3 × \(10^5\))个节点,标号从 1 到 𝑁,这 𝑁 个节点一开始相互不连通。第 𝑖 个节点的初
始权值为 𝑎𝑖(±1000),接下来有如下一些操作( 3 × \(10^5\) 个):
U x y:加一条边,连接第 𝑥 个节点和第 𝑦 个节点
A1 x v :将第 𝑥 个节点的权值增加 𝑣
A2 x v :将第 𝑥 个节点所在的连通块的所有节点的权值都增加 𝑣
A3 v :将所有节点的权值都增加 𝑣 • F1 x :输出第 𝑥 个节点当前的权值
F2 x :输出第 𝑥 个节点所在的连通块中,权值最大的节点的权值
F3 :输出所有节点中,权值最大的节点的权值

我 们 可 以 离 线 做!!!

利用线段树操作就必须要保证每一次区间操作时其对应的线段树编号都是连续的或是一段一段的,树链剖分便是这个思想

于是我们考虑离线利用并查集和链表对所有点进行重新排序。保证每时刻连通的点编号都连续即可。至于重新编号的方法:预先跑一遍输入,将连通块以链表首尾相接的方式合并,跑完以后顺次标号即可。

7. 存在终态的问题

T14

洛谷 上帝造题的 7 天

对区间每个数开根,单点或区间求和。
• 当到达操作终态后进行标记,避免冗余修改和查询。
(也就是记录一个区间是否全部达到终态的懒标记)

posted @ 2021-08-03 20:28  lei_yu  阅读(195)  评论(0编辑  收藏  举报