其他分享
首页 > 其他分享> > 树链剖分

树链剖分

作者:互联网

目录

树链剖分

前言

我认为树链剖分是一种工具而不是数据结构

它能让你处理树上的链的操作

感觉像是 序列 \(\rightarrow\) 树 的一种媒介,序列问题 \(+\) 树剖 \(=\) 树上问题

是这样没错了

模板P3384

题意

给你一颗树,需要支持以下操作:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=4e5+5;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
#define lsp p<<1
#define rsp p<<1|1
int n,m,r,mod;
int tot=1,ver[N],edge[N],nxt[N],head[N];
int w[N],wt[N];
int t[N<<2],lz[N<<2];
int son[N],id[N],fa[N],cnt,dep[N],siz[N],top[N];
int res=0;
inline void add(int x,int y,int z){
	ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;
}

//segement tree
inline void push_up(int p){
	t[p]=(t[lsp]+t[rsp])%mod;
}
inline void push_down(int p,int len){
	lz[lsp]+=lz[p],lz[rsp]+=lz[p];
	t[lsp]+=lz[p]*(len-(len>>1));
	t[rsp]+=lz[p]*(len>>1);
	t[lsp]%=mod,t[rsp]%=mod;
	lz[p]=0;
}
inline void build(int p,int l,int r){
	if(l==r){
		t[p]=wt[l]%mod;
		return;
	}
	int mid=l+r>>1;
	build(lsp,l,mid);
	build(rsp,mid+1,r);
	push_up(p);
}
inline int query(int p,int l,int r,int L,int R){
	int res=0;
	if(L<=l&&r<=R){
		res=(res+t[p])%mod;
		return res;
	}
	if(lz[p])
		push_down(p,r-l+1);
	int mid=l+r>>1;
	if(L<=mid) res=(res+query(lsp,l,mid,L,R))%mod;
	if(R>mid) res=(res+query(rsp,mid+1,r,L,R))%mod;
	return res%mod;
}
inline void update(int p,int l,int r,int L,int R,int k){
	if(L<=l&&r<=R){
		lz[p]=(lz[p]+k)%mod;
		t[p]=(t[p]+(r-l+1)*k)%mod;
		return;
	}
	if(lz[p]) push_down(p,r-l+1);
	int mid=l+r>>1;
	if(L<=mid) update(lsp,l,mid,L,R,k);
	if(R>mid) update(rsp,mid+1,r,L,R,k);
	push_up(p);
}

//tree dfs
inline void dfs1(int x,int f){
	fa[x]=f,dep[x]=dep[f]+1,siz[x]=1;
	int maxson=-1;
	for(int i=head[x];i;i=nxt[i]){
		int to=ver[i];
		if(to==f) continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[to]>maxson) son[x]=to,maxson=siz[to];
	}
}
inline void dfs2(int x,int topf){
	id[x]=++cnt,wt[cnt]=w[x];
	top[x]=topf;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=nxt[i]){
		int to=ver[i];
		if(to==son[x]||to==fa[x]) continue;
		dfs2(to,to);
	}
}
inline void query_upd(int x,int y,int k){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	update(1,1,n,id[x],id[y],k);
}
inline int query_sum(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=(ans+query(1,1,n,id[top[x]],id[x]))%mod;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])
		swap(x,y);
	return (ans+query(1,1,n,id[x],id[y]))%mod;
}
inline int query_son(int x){
	return query(1,1,n,id[x],id[x]+siz[x]-1);
}
inline void upd_son(int x,int k){
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}

signed main(){
	n=read(),m=read(),r=read(),mod=read();
	for(int i=1;i<=n;++i)
		w[i]=read();
	for(int i=1,u,v;i<n;++i){
		u=read(),v=read();
		add(u,v,1),add(v,u,1);
	}
	dep[0]=1;
	dfs1(r,0);
	dfs2(r,r);
	build(1,1,n);
	while(m--){
		int op=read(),x,y,z;
		if(op==1){
			x=read(),y=read(),z=read();
			query_upd(x,y,z%mod);
		}
		if(op==2){
			x=read(),y=read();
			printf("%lld\n",query_sum(x,y)%mod);
		}
		if(op==3){
			x=read(),y=read();
			upd_son(x,y%mod);
		}
		if(op==4){
			x=read();
			printf("%lld\n",query_son(x)%mod);
		}
	}
}

P2590

题意

给你一颗树,需要支持以下操作:

思路

树链剖分 \(+\) 单点修改区间查询线段树

code

P3178

题意

给你一颗树,需要支持以下操作:

思路

树链剖分 \(+\) 区间修改区间查询线段树

code

P3833

题意

给你一颗树,需要支持以下操作:

思路

树链剖分 \(+\) 区间修改区间查询线段树

code

P2146

题意

给你一颗树,需要支持以下操作:

思路

树链剖分 \(+\) 区间推平区间查询线段树

code

P4114

题意

给你一颗树,需要支持以下操作:

思路

边权树剖

考虑把父亲与儿子之间的边权变成儿子的点权

QUERY 操作时查询点权的 \(max\) 就行

注意:不能算 \(LCA(a,b)\) 的点权

code

P4315

题意

给你一颗树,需要支持以下操作:

思路

边权转点权树剖

有两种区间修改操作,\(lz1\) 是推平的懒标记,\(lz2\) 是区间加的懒标记

细节:优先进行推平,推平时把更新的左右儿子的 \(lz2\) 改为 \(0\)

code

P1505

题意

给你一颗树,需要支持以下操作:

思路

树链剖分

code

CF343D

题意

给你一颗树,需要支持以下操作:

思路

树链剖分

code

CF877E

题意

给你一颗树,需要支持以下操作:

思路

树链剖分

code

P6157

题意

给你一颗树,第 \(i\) 个点的点权为 \(w_i\)。

每一次给出一条链,小 A 可以从这条链上找出两个点权不同的点 \(x,y\),他的得分是 \(w_x\bmod w_y\)

然后小 B 会从整棵树中选取两个小 A 没有选过的点,计分方式同小 A

求小 A 得分最大值,与在此情况下小 B 的得分最大值

有时会有增加一个点的权值的操作

思路

小 \(A\) 的最大值一定是链内严格第二大,用树剖维护

小 \(B\) 的得分可用 \(multiset\) 维护

code

P3979

题意

给你一颗树,需要支持以下操作:

思路

先以 \(1\) 为根树剖

操作 \(1,2\) 十分容易

操作 \(3\) 分情况讨论:(记根为 \(now\),查询的城市为 \(x\))

对于 \(3\) ,我们找到 \(x\rightarrow now\) 链上 \(x\) 的儿子 \(y\)

发现以 \(now\) 为根的时候 \(y\) 的子树计算不到,扣掉就行

code

P2486

题意

给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:

  1. 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\) 和 \(b\))都染成颜色 \(c\)。
  2. 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:112221

思路

前面的题都是照着板子打的 (因为板子不变),这道题能够帮人更好的理解树剖的本质

线段树需要维护 \(lc\) (左端点颜色) \(rc\) (右端点颜色) \(sum\) (颜色段数量)

合并左右两子区间的时候,\(t[p].sum=t[lsp].sum+t[rsp].sum-(t[lsp].rc=t[rsp].lc)\)

对于路径求和的部分,我们不能简单的把 \(sum\) 累加

在左右两点不断向上跳的过程中,左边跳的区间是连续的,右边也是

因此需要在加之后判断两边接上的区间左右端点颜色是否相同,若相同答案 \(--\)

这个需要在跑线段树的时候记录一下 \(Lc\) 和 \(Rc\)

注意:跳到最顶上了也要判断一下

code

标签:code,题意,剖分,int,边权,树链,点权,节点
来源: https://www.cnblogs.com/into-qwq/p/16437080.html