ZKW线段树区间加区间取最值

本来以为是和zkw单点修改之类的简单技巧,但是今天卡常的时候学习了一下发现有点离谱。
树状数组的区间加区间求和是利用差分和一次前缀和来完成的,但是写在线段树上就不用差分了。
但是可以差分,并且zkw线段树提供了一种线段树式的差分方法:
对于每个点,只存一个标记 t r x tr_x trx,表示 max ⁡ f a x − max ⁡ x \max_{fa_x} - \max_{x} maxfaxmaxx,也就是线段树上一个节点和他父亲区间最大值的差分,我们现在可以知道一个线段树上一个整区间的最大值就是他到根的和。
那么我们求区间最大值,就是对于一个区间分成的 log ⁡ n \log n logn个整区间分别求到根的和。
可以通过简单的技巧在极小常数的 O ( log ⁡ n ) O(\log n) O(logn)内完成,具体看代码。
区间加 x x x,那么我们找到区间最左边的点的叶子和最右边的点的叶子。
然后一起往上爬,如果左边的那个点是父亲的左儿子,那么右儿子的 t r tr tr需要加上 x x x。(右边同理)
但是这样修改之后父亲的最大值可能会变化,他的 t r tr tr就不对了,那么我们需要维护父亲的 t r tr tr,具体看代码。
所以这种线段树式的差分使得我们可以抛弃懒标记,从而使常数和树状数组一样优秀。
打懒标记的线段树在比较差的评测机上是一秒跑不过1e6的,这也是写这篇博客的目的,大概常数是一般线段树的 3 5 \frac 35 53

C o d e \mathcal Code Code

#define lc u<<1
#define rc lc|1
int mx[maxn << 2] , ans[maxn] , M;

inline void upd(int u){ int tmp = max(mx[lc] , mx[rc]);  mx[lc] -= tmp , mx[rc] -= tmp , mx[u] += tmp; }
void add(int l,int r){
	for(l += M - 1 , r += M + 1;l ^ r ^ 1;l >>= 1 , r >>= 1){
		if(~l & 1) mx[l ^ 1] ++;
		if(r & 1) mx[r ^ 1] ++;
		upd(l >> 1) , upd(r >> 1);
	}
	for(;l != 1;l >>= 1)upd(l >> 1);
}
int qry(int l,int r){
	int la = 0 , ra = 0;
	l += M , r += M;
	for(;(l^r) > 1;l >>= 1 , r >>= 1){
		la += mx[l] , ra += mx[r];
		if(~l & 1) la = max(la , mx[l ^ 1]);
		if(r & 1) ra = max(ra , mx[r ^ 1]);
	}
	int ans = max(la + mx[l] , ra + mx[r]);
	for(;l > 1;) ans += mx[l >>= 1];
	return ans;
}

你可能感兴趣的:(线段树)