ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

轻重链剖分 学习笔记

2021-02-15 22:01:57  阅读:202  来源: 互联网

标签:链剖分 int siz ll 笔记 num maxn 节点 轻重


目录

板子题

题目传送门
这道题目要求在树上修改和查询点一条链上或者是一棵子树的点的权值。

算法解析

考虑使用LCA,但是不能使用倍增的解法(倍增只能查询不能修改),所以我们要使用一种新的算法——轻重链剖分。
建议先学完LCA在来看这篇文章。

定义

这里列出一些必要的定义:

  1. 重儿子:子节点最多的一个儿子。
  2. 轻儿子:一个节点的儿子除重儿子之外的儿子。
  3. 重边:一段为重儿子的边。
  4. 轻边:除重边以外的边。
  5. 重链:由重边组成的链,由轻儿子为起点。

实现

预处理

预处理由两次dfs组成。
第一次dfs需要处理以下数组:
\(fa,son,siz,d\) 分别代表节点的 父亲,重儿子,子树节点数,深度。
第二次dfs需要处理以下数组:
\(id,top,a\) 分别代表节点的新编号(按照dfs达到的顺序),这个节点所在的重链的顶端,新编号的点权,注意一定要先处理重儿子并且使用dfs。原因后面讲。

修改

怎么修改或者查询一条链的点权呢?
我们可以让更深的点沿着重链向上跳,直到两个点处于同一条重链上就可以了。
然后我们会发现,如果先处理重儿子并且使用dfs的时候,这样一条重链上的节点和一棵子树的节点的编号是连续的,我们就可以使用线段树来解决这个问题了。
而处理一棵子树权值的时候,我们只要将 \(id_i\) 到 \(id_i+siz_i-1]\) 这段区间进行修改或者是查询就可以了。

因为修改的点的 \(id\) 都是连续的,所以我们就可以使用线段树大法来解决了。

复杂度

处理一条链的复杂度是 \(\Theta\left(\log^2n\right)\) ,处理子树的复杂度是 \(\Theta\left(\log n\right)\) 。

代码

这里用递归式线段树来解决这个问题。

#include<cstdio>
#define maxn 100039
#define emaxn 200039
using namespace std;
typedef long long ll;

int n,T,root;
ll MOD;

int head[maxn],nex[emaxn],to[emaxn],k;
#define add(x,y) nex[++k]=head[x];\
head[x]=k;\
to[k]=y;
int tmp,x,y;
ll z;
//链式前向星
int w[maxn],a[maxn];
int siz[maxn],son[maxn],d[maxn],fa[maxn];
//size是Linux保留字
int id[maxn],top[maxn],cnt;
void dfs1(int num,int pre){
	siz[num]=1;
	fa[num]=pre;
	d[num]=d[pre]+1;
	int maxx=0;
	for(int i=head[num];i;i=nex[i])
	    if(to[i]!=pre){
	    	dfs1(to[i],num);
	    	if(siz[to[i]]>maxx) maxx=siz[to[i]],son[num]=to[i];
	    	siz[num]+=siz[to[i]];
		}
	return;
}
void dfs2(int num,int topf){
	top[num]=topf; id[num]=++cnt;
	if(!son[num]) return;
	dfs2(son[num],topf);
	for(int i=head[num];i;i=nex[i])
		if(to[i]!=fa[num] && to[i]!=son[num])
		    dfs2(to[i],to[i]);
	return;
}
//预处理 
//以下为线段树 
int L,R;
ll C;
ll sum[maxn<<2],f[maxn<<2];
void up(int rt){sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%MOD;return;}
void build(int l,int r,int rt){
	if(l==r){
		sum[rt]=a[l]%MOD;
		return;
	}
	int m=(l+r)>>1;
	build(l,m,rt<<1);
	build(m+1,r,rt<<1|1);
	up(rt);
}
void down(int ln,int rn,int rt){
	if(f[rt]){
		f[rt<<1]+=f[rt]; f[rt<<1]%=MOD;
		f[rt<<1|1]+=f[rt]; f[rt<<1|1]%=MOD;
		sum[rt<<1]+=f[rt]*ln; sum[rt<<1]%=MOD;
		sum[rt<<1|1]+=f[rt]*rn; sum[rt<<1|1]%=MOD;
		f[rt]=0;
	}
	return;
}
void update(int l,int r,int rt){
	if(L<=l&&r<=R){
		f[rt]+=C;
		sum[rt]+=C*(r-l+1);
		return;
	}
	int m=(l+r)>>1;
	down(m-l+1,r-m,rt);
	if(m>=L) update(l,m,rt<<1);
	if(m<R) update(m+1,r,rt<<1|1);
	up(rt);
}
ll find(int l,int r,int rt){
	if(L<=l&&r<=R)
	    return sum[rt];
	int m=(l+r)>>1;
	ll s=0;
	down(m-l+1,r-m,rt);
	if(m>=L) s=(s+find(l,m,rt<<1))%MOD;
	if(m<R) s=(s+find(m+1,r,rt<<1|1))%MOD;
	return s;
}
//以上为线段树 
void init(){
	dfs1(root,0);
	dfs2(root,root);
	for(int i=1;i<=n;i++)
	    a[id[i]]=w[i];
	build(1,n,1);
	return;
}
void swap(int &x,int &y) {x^=y;y^=x;x^=y;}//交换 
void updateR(int u,int v,ll c){
    C=c;
	while(top[u]!=top[v]){
		if(d[top[u]]<d[top[v]]) swap(u,v);
		L=id[top[u]]; R=id[u]; 
		update(1,n,1);
		u=fa[top[u]];
	}
	if(d[u]<d[v]) swap(u,v);
	L=id[v]; R=id[u];
	update(1,n,1);
	return;
}
ll findR(int u,int v){
	ll ans=0;
	while(top[u]!=top[v]){
		if(d[top[u]]<d[top[v]]) swap(u,v);
		L=id[top[u]]; R=id[u];
		ans+=find(1,n,1);
		ans%=MOD;
		u=fa[top[u]];
	}
	if(d[u]<d[v]) swap(u,v);
	L=id[v]; R=id[u];
	ans+=find(1,n,1);
	return ans;
}
void updateS(int rt,ll c){
	L=id[rt]; R=id[rt]+siz[rt]-1; C=c;
	update(1,n,1);
}
ll findS(int rt){
	L=id[rt]; R=id[rt]+siz[rt]-1;
	return find(1,n,1);
}
int main(){
	scanf("%d%d%d%lld",&n,&T,&root,&MOD);
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=1;i<n;i++){
    	scanf("%d%d",&x,&y);
    	add(x,y) add(y,x)
	}
	init();
	while(T--){
		scanf("%d",&tmp);
		if(tmp==1){
			scanf("%d%d%lld",&x,&y,&z);
			updateR(x,y,z);
		}
		else if(tmp==2){
			scanf("%d%d",&x,&y);
			printf("%lld\n",findR(x,y)%MOD);
		}
		else if(tmp==3){
			scanf("%d%lld",&x,&z);
			updateS(x,z);
		}
		else{
			scanf("%d",&x);
			printf("%lld\n",findS(x)%MOD);
		}
	}
	return 0;
}

标签:链剖分,int,siz,ll,笔记,num,maxn,节点,轻重
来源: https://www.cnblogs.com/jiangtaizhe001/p/14405536.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有