【数据删除】树

王队线段树

又名:兔队线段树,楼房重建线段树,单调栈维护前缀最值/单调栈

P4198 - 楼房重建

https://www.luogu.com.cn/problem/P4198

显然我们转化成斜率之后,要求的就是前缀最大值个数。

我们设 \(tre[l,r]\) 代表考虑 \([l,r]\) 的所有点,\([mid+1,r]\) 的前缀最大值个数。

\(\max[l,r]\)\([l,r]\) 间的最大值。

再设 \(c([l,r],x)\)\([l,r]\) 之内只考虑 \(\ge x\) 的点,的前缀最大值个数。

显然 \(tre[l,r]=c([mid+1,r],\max[l,mid])\)

这个 \(c\) 就像一个 query 函数,怎么样求出呢。

分情况讨论:

  • \(\max[lson]< x\)\(c([l,r],x)=c([mid+1,r],x)\)
  • 否则 \(c([l,r],x)=tre[l,r]+c([l,mid],x)\)

答案就是 \(c([1,n],0)\) 啦!

这个显然是 \(O(\log^2)\) 的。

P4425 - [HNOI/AHOI2018]转盘

https://www.luogu.com.cn/problem/P4425

待填

诚哥线段树

又名:线段树维护历史信息。

当然,这种问题用分块就很好解决,因为不用 pushdown。

https://www.cnblogs.com/LightningUZ/p/14726808.html

区间历史最大值

操作 1:区间加上一个数字 \(x\)

操作 2:查询区间历史最大值。

我们维护每个节点的:

当前的区间最大值 mxa,历史最大值 hmxa,lazy 的值 laz,lazy 的历史最大值 hlaz

画一下操作轴:

+x+x+x+Xmax+x  +x+x+Xmax+x+x
我的操作         我的父亲的操作
Xmax为历史最大值的位置。

我们现在要将父亲的操作 pushdown 到我的节点上。

那么

hmxa[o]=max(hmxa[o],mxa[o]+hlaz[fa]);
hlaz[o]=max(hlaz[o],laz[o]+hlaz[fa]);
mxa[o]+=mxa[fa];
laz[o]+=laz[fa];

即可。注意代码顺序不能变,否则会用自己的信息来更新。

P4314 - CPU 监控

https://www.luogu.com.cn/problem/P4314

也就是区间加,区间覆盖,区间历史最大值。

发现第一次区间覆盖后的操作,无论是加还是覆盖,都可以视为区间覆盖。因为第一次区间覆盖后全部数字都相同了,设为 \(x\),那么加 \(c\) 就相当于覆盖为 \(x+c\)。(也就是直接将赋值标记 +c 即可)。

所以一个点对应的操作序列必定是一段区间加,一段区间覆盖。

于是分类讨论一下,是父亲的加操作会更新我的历史最大值,还是父亲的覆盖操作会更新我的历史最大值即可。

比如我的操作是这样的:+x+x+x+x...=x=x=x=x 设我的历史最大值为 Imax
1. 父亲加操作 来更新 [+x+x+x...=x=x=x] [+x+Xmax+x...=x=x=x]
   那就和上文一样用 Xmax 加上我的和
2. 父亲覆盖操作 来更新 [+x+x+x...=x=x=x] [+x+x+x...=x=Xmax=x]
   那么就直接 chkmax 我的值

所以我们也要记录 fz,hfz 代表赋值的 laz 以及赋值的 laz 的历史最大值。

区间历史版本和

https://www.cnblogs.com/guangheli/p/13274276.html

操作 1:区间加上一个数字 \(x\)

操作 2:查询区间历史版本和。

先想想普通的线段树:维护 \(sum\) 代表区间和,维护 \(laz\) 代表懒标记。

现在我们多维护:

\(hsum\) 代表历史区间和。

\(tag\) 代表距离上一次被更新时间,有多少时间了;或者说,使得 \(hsum:=hsum+tag\times sum\)\(tag\)

但是我们还需要维护一个东西,叫 \(dlt\)。比如我们两次操作都是落在一个区间,一次 +5,一次+7,那么我们会将 \(laz=12,tag=2\) 给 pushdown,这显然是不对的,我们需要减去 \(dlt\) 以抵消算多的部分。计算 \(dlt\) 的方式为,如果我个区间 +val,区间的 \(tag\)\(x\)(此时 \(tag\) 还没被更新)那么 \(dlt+=x*val\)。比如 +5 和 +7,那么先有 dlt+=5X0,然后有 dlt+=7X1,那么 pushdown 的时候,就会是 \(hsum:=hsum+tag*sum=hsum+3*12\),减去 \(dlt\) 后就是 \(3*12-dlt*tag=3*12-7*3=15\) 演算无误,完美无瑕。

下传标记顺序为 \(laz,dlt,tag\)

伪代码如下:

并没有经过调试,没有确保正确性。

struct mist{int sum,hsum,laz,tag,dlt;}tre[];

void pddlt(int o,int len,int val){
	tre[o].dlt+=val;
}

void pdlaz(int o,int len,int val){
	tre[o].dlt-=val*tre[o].tag;
	tre[o].laz+=val;
	tre[o].sum+=val*len;
}

void pdtag(int o,int val){
	tre[o].tag+=val;
	tre[o].hsum+=val*tre[o].sum;
}

void pudown(int o,int l,int r){
	int mid=(l+r)>>1;
	if(tre[o].laz){
		pdlaz(o<<1,mid-l+1,tre[o].laz);
		pdlaz(o<<1|1,r-mid,tre[o].laz);
		tre[o].laz=0;	
	}
	if(tre[o].dlt){
		pddlt(o<<1,mid-l+1,tre[o].dlt,1);
		pddlt(o<<1|1,r-mid,tre[o].dlt,1);
		tre[o].dlt=0;
	}
	if(tre[o].tag){
		pdtag(o<<1,tre[o].tag);
		pdtag(o<<1|1,tre[o].tag);
		tre[o].tag=0;
	}
}

void puhigh(int o){
	tre[o].sum=tre[o<<1].sum+tre[o<<1|1].sum;
	tre[o].hsum=tre[o<<1].hsum+tre[o<<1|1].hsum; 
}

void updat(...){
	if(L<=l&&r<=R){
		pdlaz(o,r-l+1,val);
		return;
	}
}

int query(...){
	if(L<=l&&r<=R){
		return tre[o].hsum;
	}
}

int main(){
	while(T--){
		//after modify do this:
		pdtag(1,1);
	}
	return 0;
}

俊摊线段树

又名:主席树合并。(假的,别当真)

P3302 - [SDOI2013]森林

https://www.luogu.com.cn/problem/P3302

前置知识:如何做到树上查询第 k 小?

P2633 - Count on a tree https://www.luogu.com.cn/problem/P2633

很简单,一个点的主席树由其父亲继承而来。

\(T_x\) 为第 \(x\) 棵线段树。

类比区间上查询第 k 小是 \(T_r-T_{l-1}\),我们可以得到:

设我们要查 \(p,q\) 的第 k 小,则答案为 \(T_p+T_q-T_{lca}-T_{fa(lca)}\)

那么如何 Link 两个点呢?直接暴力地 Link 即可,用启发式合并,每次更新的是较小的树,可以做到 \(O(\log n)\)(题解说法)。

(但是我觉得时间应该是 \(O(\log^2 n)\) 毕竟 1 每到一个点都要用 \(O(\log n)\) 时间插入主席树;2 要重构倍增数组才能求 LCA)

posted @ 2022-03-17 14:29  BlankAo  阅读(135)  评论(0编辑  收藏  举报