zoukankan      html  css  js  c++  java
  • [洛谷P3384][题解]轻重链剖分

    0.序

    是一道模板题,写这篇题解是为了骗取阅读量把我对树剖的见解写出来+巩固。

    1.概述

    树链剖分是一种毒瘤算法,她可以用来解决一些树上毒瘤问题。
    有这样一群毒瘤出题人,他们把序列上很简单的问题搬到了树上,于是就有了树链剖分。
    基本思路是将整棵树剖分成轻重链,然后用毒瘤数据结构分别维护,所以树链剖分(approx)优雅的暴力。
    先给出几个定义:
    重儿子:一个节点子节点最多的儿子。
    轻儿子:除了重儿子之外的儿子。
    重边:父亲连向重儿子的边。
    轻边:不是重边的边。
    重链:重边连起来的链。特别地,两端均连轻边的节点自成一条重链。
    轻链:非重链的路径。所有轻链的长度都是1。
    还有几个数组:
    hvs[]:当前节点的重儿子。
    ltp[]:当前节点所在重链的顶端(深度最小)。

    两次DFS:
    第一次:求fa、dep、siz等基本数组和hvs。hvs可以直接DFS每个节点比较求出,相信学到树剖的大佬已经会了,不多讲。
    第二次:求ltp数组。如果当前节点是重儿子,直接继承当前重链头;否则以当前节点为重链头,继续DFS。

    DFS完之后,我们可爱的小树已经被拍扁成DFS序列了,接下来只需用线段树/树状数组等毒瘤数据结构来维护即可。
    给出几个例子:
    1.更改/查询x到y的路径
    只需按照求LCA的步骤,依次上跳。注意树剖的上跳是u=fa[ltp[u]],也就是一次跳一条重链+轻链,可以思考一下她的复杂度。
    因为DFS序里一条链上的编号都是连续的,所以直接线段树修改/查询。
    给出修改的例子:

    while(ltp[u]!=ltp[v]){
    	if(dep[ltp[u]]<dep[ltp[v]])swap(u,v);
    	Modify(1,idx[ltp[u]],idx[u],add);
    	u=fa[ltp[u]];
    }
    if(dep[u]>dep[v])swap(u,v);
    Modify(1,idx[u],idx[v],add);
    

    2.更改/查询x的子树
    容易发现,一棵子树在DFS序列里也是连续的,所以直接维护即可。
    给出修改的例子:

    Modify(1,idx[u],idx[u]+siz[u]-1,add);
    

    具体实现细节可以参考代码。

    2.代码

    #define N 100010
    int n,m,r,p,tot;
    int siz[N],fa[N],dep[N],ipt[N];
    int hvs[N],ltp[N],idx[N],num[N];
    struct Edge {
    	int to,nxt;
    }e[N<<1];
    int head[N],cnt;
    inline void ade(int u,int v){
    	e[++cnt].to=v;
    	e[cnt].nxt=head[u];
    	head[u]=cnt;
    }
    struct Node {
    	int l,r,tag,wei;
    }tr[N<<2];
    void DFS1(int now,int ff){
    	fa[now]=ff;
    	siz[now]=1;
    	dep[now]=dep[ff]+1;
    	for(rg int i=head[now];i;i=e[i].nxt){
    		int v=e[i].to;
    		if(v!=ff){
    			DFS1(v,now);
    			siz[now]+=siz[v];
    			if(siz[v]>siz[hvs[now]])hvs[now]=v;
    		}
    	}
    }
    void DFS2(int now,int tp){
    	ltp[now]=tp;
    	idx[now]=++tot;
    	num[tot]=ipt[now];
    	if(hvs[now])DFS2(hvs[now],tp);
    	for(rg int i=head[now];i;i=e[i].nxt){
    		int v=e[i].to;
    		if(v!=fa[now]){
    			if(v!=hvs[now])DFS2(v,v);
    		}
    	}
    }
    void Build(int k,int l,int r){
    	tr[k].l=l,tr[k].r=r;
    	if(tr[k].l==tr[k].r){
    		tr[k].wei=num[l]%p;
    		return;
    	}
    	Build(ls,l,nmid);
    	Build(rs,nmid+1,r);
    	pushup;
    }
    inline void Pushdn(int k){
    	if(tr[k].tag){
    		tr[ls].wei+=tr[k].tag*(tr[ls].r-tr[ls].l+1)%p;
    		tr[rs].wei+=tr[k].tag*(tr[rs].r-tr[rs].l+1)%p;
    		tr[ls].wei%=p,tr[rs].wei%=p;
    		tr[ls].tag+=tr[k].tag%p;
    		tr[rs].tag+=tr[k].tag%p;
    		tr[ls].tag%=p,tr[rs].tag%=p;
    		tr[k].tag=0;
    	}
    }
    void Modify(int k,int l,int r,int add){
    	if(l<=tr[k].l&&tr[k].r<=r){
    		tr[k].wei+=add%p*(tr[k].r-tr[k].l+1)%p;
    		tr[k].tag+=add%p;
    		tr[k].wei%=p,tr[k].tag%=p;
    		return;
    	}
    	Pushdn(k);
    	if(l<=tmid)Modify(ls,l,r,add);
    	if(tmid<r)Modify(rs,l,r,add);
    	pushup;
    }
    int Query(int k,int l,int r){
    	if(l<=tr[k].l&&tr[k].r<=r){
    		return tr[k].wei%p;
    	}
    	Pushdn(k);
    	int res=0;
    	if(l<=tmid)res=(res+Query(ls,l,r))%p;
    	if(tmid<r)res=(res+Query(rs,l,r))%p;
    	return res;
    }
    void Opt1(int u,int v,int add){
    	add%=p;
    	while(ltp[u]!=ltp[v]){
    		if(dep[ltp[u]]<dep[ltp[v]])swap(u,v);
    		Modify(1,idx[ltp[u]],idx[u],add);
    		u=fa[ltp[u]];
    	}
    	if(dep[u]>dep[v])swap(u,v);
    	Modify(1,idx[u],idx[v],add);
    }
    int Opt2(int u,int v){
    	int res=0;
    	while(ltp[u]!=ltp[v]){
    		if(dep[ltp[u]]<dep[ltp[v]])swap(u,v);
    		res+=Query(1,idx[ltp[u]],idx[u]);
    		res%=p,u=fa[ltp[u]];
    	}
    	if(dep[u]>dep[v])swap(u,v);
    	return (res+Query(1,idx[u],idx[v]))%p;
    }
    void Opt3(int u,int add){
    	add%=p;
    	Modify(1,idx[u],idx[u]+siz[u]-1,add);
    }
    int Opt4(int u){
    	return Query(1,idx[u],idx[u]+siz[u]-1)%p;
    }
    signed main(){
    	Read(n),Read(m),Read(r),Read(p);
    	for(rg int i=1;i<=n;i++)Read(ipt[i]);
    	for(rg int i=1;i<n;i++){
    		int u,v;
    		Read(u),Read(v);
    		ade(u,v),ade(v,u);
    	}
    	DFS1(r,0);
    	DFS2(r,r);
    	Build(1,1,n);
    	for(rg int i=1;i<=m;i++){
    		int opt,x,y,z;
    		Read(opt),Read(x);
    		switch(opt){
    			case 1:{
    				Read(y),Read(z);
    				Opt1(x,y,z);
    				break;
    			}
    			case 2:{
    				Read(y);
    				cout<<Opt2(x,y)%p<<endl;
    				break;
    			}
    			case 3:{
    				Read(z);
    				Opt3(x,z);
    				break;
    			}
    			case 4:{
    				cout<<Opt4(x)%p<<endl;
    				break;
    			}
    		}
    	}
    	return 0;
    }
    

    #define int LL
    缺省源又臭又长放到其他blog里了。

    3.总结

    其实树剖听起来很毒瘤然而理解了就很简单了。
    她可以解决许多想不出正解/十分毒瘤的树上问题。
    树剖的代码看着长其实是数据结构长,树剖的核心代码并不长,而且很容易理解。

    4.完结撒花

  • 相关阅读:
    如何分秒必争--浅淡时间切片器
    2019微软Power BI 每月功能更新系列——Power BI 6月版本功能完整解读
    2019微软Power BI 每月功能更新系列——Power BI 5月版本功能完整解读
    自制操作系统 ---- 资料
    [书籍推荐] 编程入门学习必备
    《30天自制操作系统》笔记5 --- (Day3)进入32位模式并导入C语言
    精彩知识视频分享
    《30天自制操作系统》笔记4 --- (Day2 下节)了解如何用汇编写操作系统中的HelloWorld
    《30天自制操作系统》笔记3 --- (Day2 上节)完全解析文件系统
    新版Notepad++加十六进制查看的插件HexEditor
  • 原文地址:https://www.cnblogs.com/juruoajh/p/13164227.html
Copyright © 2011-2022 走看看