线段树Segment Tree学习笔记

请注意,不建议读者对于每道题均编代码,码量极大

T1

Description

给定一个序列,支持区间加与区间乘与区间查询和的操作。

Solution

一道线段树板子题。

我们同样维护线段树的区间信息与懒标记,只不过此时懒标记记录的有两个值,即 k k b b ,表示该节点已经乘 k k b b 了,而其子节点尚未做过此种操作。

于是,我们可以在 O ( q l o g 2 n ) O(qlog_2n) 的时间复杂度内解决本题。

T2

Description

给定一个序列,支持区间修改与每次查询在 1 i 1-i 中最后一个不小于 x x 的位置。

Solution

比较裸的线段树。

每个线段树的节点维护的均为该区间的内的最大值。区间修改的方式不变,对于题目中的这种查询,我们直接从 1 1 号节点向下进行搜索。如果该节点所代表的区间被完全包含在查询区间内,那么我们便判断,该区间的最大值是否大于等于 k k ;如果不是,就直接返回,否则看下它的两个子区间,如果它的右子区间的最大值不小于 k k ,就贪心地向右搜去寻找更大的 i i ,否则向左。

于是,本题就可以在 O ( q l o g 2 n ) O(qlog_2n) 的时间复杂度下解决。

T3

Description

给定一个序列,支持区间查询最大子段和。

Solution

考虑对于一个线段树节点,我们维护什么。

维护三个内容:

l m i n lmin : 从最左边开始连续的一段区间的最大和

l m a x lmax : 从最右边开始连续的一段区间的最大和

s m a x smax : 维护这个区间的最大子段和;

s u m v sumv : 维护这个区间各数之和。

显然,①②都符合区间加法,③可以通过其子区间的①②得到。

tree[rt].lmin=max(tree[2*rt].lmin,tree[2*rt].sumv+tree[2*rt+1].lmin)
tree[rt].lmax=max(tree[2*rt].lmin,tree[2*rt].sumv+tree[2*rt+1].lmax)
tree[rt].sumv=tree[2*rt].sumv+tree[2*rt+1].sumv
tree[rt].smax=max(tree[2*rt].smax,tree[2*rt+1].smax,tree[2*rt].rmax+tree[2*rt+1].lmax)

于是,我们便可以在 O ( q l o g 2 n ) O(qlog_2n) 的时间复杂度内解决本题,如果忽略巨大的常数的话

T4

Description

给定一个序列,支持区间取反(每个数都乘 1 -1 )和查询最大子段和。

Solution

做法与 T 3 T3 基本相同,但是还要多维护几个东西。

l m i n lmin : 从最左边开始连续的一段区间的最小和

r m i n rmin : 从最右边开始连续的一段区间的最小和

s m i n smin : 维护这个区间的最小子段和;

这三个东西样符合区间加法,上传公式与 T 3 T3 基本相同。

若区间乘 1 -1 ,我们就将该区间的最小子段和与最大子段和乘上 1 -1 后交换即可

于是时间复杂度仍为 O ( q l o g 2 n ) O(qlog_2n) 如果不考虑巨大无比的常数的话

T5

Description

给定一个序列,支持单点修改与区间查询不能组成的最小的数。

若对于一个区间 x x 能组成是指,能够在该区间中选一些数,它们的和正好等于 x x 。不能组成则相反。

n 1 0 5 , a i 1 0 7 n≤10^5, a_i≤10^7 ,时限 3000 m s 3000ms

Solution

首先,假设我们目前 1 x 1-x 这些数都能得到,现在又加入进来一个数 k k

k x + 1 k≤x+1 ,则现在 1 a i x + 1 a i 1-\sum_{a_i≤x+1} a_i 这些数就都能得到啦;否则,直接输出 x + 1 x+1 。因为,当 k x + 1 k>x+1 时,由于在看到 k k 之前 x + 1 x+1 无法得到,现在 k k 又没有组成有力的贡献,所以 x + 1 x+1 总归无法得到。

于是,我们对于区间中,第 i i 个数二进制的位数为 w i w_i 。我们每次整个扫一遍各个 i i 的值( i l o g 2 m i≤log_2m ,其中 m m 为序列中的最大值),架设对于二进制位数为 i i 的数的最小值为 k k ,若 k k 已经大于了 x + 1 x+1 ,那么就直接宣布结束并输出 x + 1 x+1 ;否则 x x 加上二进制位数为 k k 的数之和, i i 的值也同时加 1 1 。所以,我们只需要开 l o g 2 m log_2m 个线段树,维护区间最小值与区间和,那么对于询问中的查询某种二进制位数的最小值与和,我们就可以 O ( l o g 2 n ) O(log_2n) 地快速查询啦。

单点修改并没有产生多大的影响,假设把 a a 变成了 a + t a+t ,那么二进制位数同 a a 的集合中相当于单点减去了 a a ,二进制位数同 a + t a+t 的集合中相当于单点加上了 a + t a+t ,也可以用线段树轻松维护。

所以,时间复杂度为 O ( n l o g 2 n l o g 2 m ) O(nlog_2nlog_2m) ,卡常后即可通过本题。

好神仙的题

T6

Description

给定一个序列,要求支持下面三种奇怪的操作:

①区间内所有的数 a i a_i 均变成 ϕ ( a i ) \phi(a_i) ,即不大于 a i a_i 且与 a i a_i 互质的数的数量。

②区间所有的数均变为 x x

③区间查询和。

n 1 0 6 , a i 1 0 7 n≤10^6, a_i≤10^7

保证数据随机

Solution

观察一下几个操作,可以发现:

①貌似不太好搞,②③就是线段树轻松搞定的区间摊与区间查询(别跟我扯珂朵莉树)。

①既然不好搞,我们便随便用个数据上来试试看——比如这个数是 21 21 ,那么

21 12 ( ϕ ( 21 ) = 12 ) 4 2 1 1 1 21→12(\phi(21)=12)→4→2→1→1……→1

可以发现,任何一个数,经过多次做 ϕ \phi 的操作,终究会让一个数变成 1 1

那么,对于一个数,最多做多少次 ϕ \phi 的操作会让它变成 1 1 呢?如果这个数是一个奇数,那么最劣情况会将它仅仅减去 1 1 ;否则,这个数会除以 2 2 。为什么呢?因为若这个数是偶数,那么 2 , 4 , 6 2,4,6…… 均不与它互质,所以这个数就会被除以 2 2 。所以,最劣的情况就是,这个数是一个奇数,它轮流地被减一除 2 2 ,可以发现变成 1 1 的最少操作次数是 l o g 2 n log_2n 级别的。

于是,我们就可以直接用线段树来维护每个区间被修改的次数,如果达到了一定的次数就不管这个区间了,否则暴力修改;另外②③的做法是裸的线段树,这里不再解释。

只需要用线性筛预处理出每个值的 ϕ \phi 即可。时间复杂度$O()


但是,会存在一种特殊的情况——

所有数都是很坑的数,需要做 20 20 次①操作才能归 0 0 ;每做 20 20 次①操作后,就来一次区间摊,把所有数给搞回去,然后再让你做①操作,还有随时的③询问……

显然,在特殊构造的上述数据中,时间复杂度 T T 飞成 O ( n q ) O(nq)

但是,需要注意,数据纯随机,出现这种情况的概率不到亿亿亿亿分之一,并不需要担心。

故总时间复杂度为 O ( q l o g 2 n ) O(qlog_2n)

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/108009605