• 【数据删除】树


    王队线段树

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

    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)

  • 相关阅读:
    Vimdiff的用法 简单
    VIM插件使用 简单
    张子阳:大道至简,职场上做人做事做管理 简单
    VIM常用快捷键整理 简单
    从程序员到项目经理(四):外行可以领导内行吗 简单
    从程序员到项目经理(三):认识项目经理 简单
    余波:技术人员如何走出职业迷茫 简单
    技术路线的选择重要但不具有决定性 简单
    转:我在Facebook工作的十大经验分享 简单
    linux下vim的编译以及终端乱码的解决方案 简单
  • 原文地址:https://www.cnblogs.com/BlankAo/p/16017124.html
Copyright © 2020-2023  润新知