ICode9

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

树链剖分

2022-07-02 13:12:30  阅读:144  来源: 互联网

标签:code 题意 剖分 int 边权 树链 点权 节点


目录

树链剖分

前言

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

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

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

是这样没错了

模板P3384

题意

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

  • 1 x y z,表示将树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值都加上 \(z\)

  • 2 x y,表示求树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值之和

  • 3 x z,表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)

  • 4 x 表示求以 \(x\) 为根节点的子树内所有节点值之和

#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

题意

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

  • CHANGE u t : 把结点 \(u\) 的权值改为 \(t\)

  • QMAX u v: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的最大权值

  • QSUM u v: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的权值和

思路

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

code

P3178

题意

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

  • 操作 1 :把某个节点 x 的点权增加 a
  • 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a
  • 操作 3 :询问某个节点 x 到根的路径中所有点的点权和

思路

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

code

P3833

题意

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

  • A u v d :将点 \(u\) 和 \(v\) 之间的路径上的所有点的点权都加上 \(d\)
  • Q u :询问以 \(x\) 为根的子树权值和

思路

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

code

P2146

题意

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

  • install x :将 \(1\) 和 \(x\) 之间的路径上所有点的点权推平为 \(1\) ,询问有多少点权改变了
  • uninstall x :将 \(x\) 的子树中所有点点权推平为 \(0\),询问有多少点权改变了

思路

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

code

P4114

题意

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

  • CHANGE i t :把第 \(i\) 条边的边权变成 \(t\)
  • QUERY a b :输出从 \(a\) 到 \(b\) 的路径上最大的边权,当 \(a=b\) 时,输出 \(0\)

思路

边权树剖

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

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

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

code

P4315

题意

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

  • Change k w:将第 \(k\) 条边上权值改为 \(w\)

  • Cover u v w:将节点 \(u\) 与节点 \(v\) 之间的边权都改为 \(w\)

  • Add u v w:将节点 \(u\) 与节点 \(v\) 之间的边权都增加 \(w\)

  • Max u v:询问节点 \(u\) 与节点 \(v\) 之间边权最大值

思路

边权转点权树剖

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

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

code

P1505

题意

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

  • C i w 将输入的第 \(i\) 条边权值改为 \(w\)
  • N u v 将 \(u,v\) 节点之间的边权都变为相反数
  • SUM u v 询问 \(u,v\) 节点之间边权和
  • MAX u v 询问 \(u,v\) 节点之间边权最大值
  • MIN u v 询问 \(u,v\) 节点之间边权最小值

思路

树链剖分

code

CF343D

题意

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

    1. 将点 \(u\) 和其子树上的所有节点的权值改为 \(1\)
    1. 将点 \(u\) 到 \(1\) 的路径上的所有节点的权值改为 \(0\)
    1. 询问点 \(u\) 的权值

思路

树链剖分

code

CF877E

题意

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

  • get : 询问一个点 \(x\) 的子树里有多少个 \(1\)

  • pow : 将一个点 \(x\) 的子树中所有节点取反

思路

树链剖分

code

P6157

题意

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

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

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

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

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

思路

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

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

code

P3979

题意

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

  • \(opt=1\),把首都修改为 \(id\)

  • \(opt=2\),将 \(x\ y\) 路径上的所有城市的防御值修改为 \(v\)

  • \(opt=3\),询问以城市 \(id\) 为根的子树中的最小防御值

思路

先以 \(1\) 为根树剖

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

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

  • \(x=rt\),返回 \(t[1].mn\)
  • \(now\) 不在 \(1\) 为根时 \(x\) 的子树内,这时候没有影响,直接统计
  • \(x\) 为 \(now\) 祖先

对于 \(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

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

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

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

ICode9版权所有