树链剖分

树链剖分其实就是把树分成一个一个的链,方便用线段树维护。

轻重边剖分

我们将树中的边分成两种:轻边,重边。
如下图中加粗的边是重边,其余是轻边。

对于每一个节点,我们都记size最大的儿子连的边为重边。
【注意:每一个点都连着重边。】

轻重边有几个性质:

性质 1:
如果(u,v)为轻边,则size(v) <= size(u) / 2。

证明:
设size(v) > size(u) / 2,则size(v)必然比其他儿子的size要大,
那么(u,v)必然为重边,与(u,v)为轻边矛盾。

性质 2:
从根到某一点 V 的路径上的轻边个数不大于O(log n)。

证明:
V 为叶子节点时轻边个数最多。
由性质1 可知,每经过一条轻边,子树的节点个数至少比原来少一半,
所以至多经过O(log n)条轻边就到达叶子节点了。

性质3:
我们称某条路径为重路径(链),当且仅当它全部由重边组成。
那么对于每个点到根的路径上都不超过O(log n)条轻边和O(log n)条重路径。

证明:
显然每条重路径的起点和终点都是由轻边构成,
而由性质2 可知,每个点到根节点的轻边个数为O(log n),
所以重路径个数也为O(log n)。

同时我们也容易发现,一个点在且只在一条重路径上,而每条重路径一定是一条从根结点方向向叶结点方向延伸的深度递增的路径。

————————————————————————————————————

轻重边剖分的过程可以使用两次dfs 来实现,
有时为了防止递归过深栈溢出,也会使用bfs 的方法来进行剖分。
剖分过程中要计算如下7 个值:
在这里插入图片描述

第一遍dfs 时可以计算前四个值,第二遍dfs 可以计算后三个值。
而计算pos 时,同一条重路径上的点需要按顺序排在连续的一段位置,也就是一段区间,而同于dfs的是每一棵子树内部的pos序列也是连续的。

树链剖分版LCA

对于 u,v,(假设dep[u]>dep[v])
如果它们不在同一条重路径,即top[u]!=top[v];
那么就将u跳至fa[top[u]],再继续往下搜。
一直到它们在同一条路径上为止,那么lca(u,v)就是深度较浅的那个。

例题

树链剖分模板题

我们可以只用一颗线段树来维护每一个节点的值,而不是边。

具体操作就不讲了,看代码就懂了。

注意事项:

1.读入优化x要清理
2. 线段树的名字不要和加边重了
3. 不仅要记录pos,还要记录pos_ed也就是每个子树结束的位置
4. 计算重儿子的各种值时要用son[u]下标
5. 建树函数的sum要用a[idx[l]]
6. build要在dfs后
7. 注意不同根节点的初始化init()
8. 注意不停的mod,而且乘的时候注意 1LL*
9. 下放lazy标记的时候,注意是+=不是=
10. pathadd注意要return
11. pathadd和pathquery不用pos,但add和query必须要用pos

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
const int N=100010;
struct edge{
	int u,v,nxt;
}e[(N<<1)];
int first[N],cnt=0;
inline void add(int u,int v){
	e[++cnt].u=u;e[cnt].v=v;
	e[cnt].nxt=first[u];first[u]=cnt;
}
int siz[N],fa[N],dep[N],son[N],pos[N],idx[N],top[N],pos_ed[N],tot=1;
void dfs1(int u){
	//cout<<u;
	siz[u]=1;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;//计算重儿子
	}
}
void dfs2(int u){
	if(son[u]){//有重儿子要先递归重儿子,保证重路径在序列中是连续一段区间
		pos[son[u]]=++tot;
		idx[pos[son[u]]]=son[u]; //
		top[son[u]]=top[u];
		dfs2(son[u]);
	}
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa[u]||v==son[u])continue;
		pos[v]=++tot;
		idx[pos[v]]=v;
		top[v]=v;
		dfs2(v);
	}
	pos_ed[u]=tot;
}
#define ll long long
#define lc (p<<1)
#define rc (p<<1|1)
int n,m,s,mod,a[N]; 
struct node{
	int l,r,lazy,sum;
}t[(N<<2)];
void build(int p,int l,int r){
	t[p].l=l;t[p].r=r;t[p].lazy=0;
	if(l==r){
		t[p].sum=a[idx[l]]%mod;//
		return;
	}
	int mid=l+r>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	t[p].sum=(t[lc].sum+t[rc].sum)%mod;
}
void init(){//cout<<s;
	dfs1(s);
	top[s]=idx[1]=s;
	pos[s]=tot=1;
	dfs2(s);
	build(1,1,n);//
}
void pushdown(int p){
	if(!t[p].lazy)return;
	t[lc].sum=(t[lc].sum+1ll*t[p].lazy*(t[lc].r-t[lc].l+1)%mod)%mod;
	t[lc].lazy=(t[lc].lazy+t[p].lazy)%mod;
	t[rc].sum=(t[rc].sum+1ll*t[p].lazy*(t[rc].r-t[rc].l+1)%mod)%mod;
	t[rc].lazy=(t[rc].lazy+t[p].lazy)%mod;
	t[p].lazy=0; 
}
void add1(int p,int l,int r,int v){
	if(l<=t[p].l&&t[p].r<=r){
		t[p].sum=(t[p].sum+1ll*(t[p].r-t[p].l+1)*v%mod)%mod;
		t[p].lazy=(t[p].lazy+v)%mod;
		return;
	}
	pushdown(p);
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid)add1(lc,l,r,v);//
	if(mid<r)add1(rc,l,r,v);
	t[p].sum=(t[lc].sum+t[rc].sum)%mod;
}
int query(int p,int l,int r){
	if(l<=t[p].l&&t[p].r<=r)
		return t[p].sum;
	pushdown(p);
	int mid=t[p].l+t[p].r>>1,ans=0;
	if(l<=mid)ans=(ans+query(lc,l,r))%mod;
	if(mid<r)ans=(ans+query(rc,l,r))%mod;
	return ans;
}
void pathadd(int u,int v,int w){
	//cout<<u<<" "<<v<<endl;
	if(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]])swap(u,v);//此处是top[u]!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		pathadd(fa[top[u]],v,w);
		add1(1,pos[top[u]],pos[u],w);
		return;
	}
	//cout<<u<<" "<<v<<endl;
	if(dep[u]>dep[v])swap(u,v);
	add1(1,pos[u],pos[v],w);
}
int pathquery(int u,int v){//询问(u,v)这条路径的和
	if(top[u]!=top[v]){// 不在同一条重链
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		return (pathquery(fa[top[u]],v)+query(1,pos[top[u]],pos[u]))%mod;
	}
	if(dep[u]>dep[v])swap(u,v);
	return query(1,pos[u],pos[v]);
}
int main(){
	//freopen("1.in","r",stdin);
	n=read();m=read();s=read();mod=read();
	//cout<<n<<m<<s;
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1;i<n;i++){
		int u,v;
		u=read();v=read();
		add(u,v);add(v,u);
	}
	init();
	//for(int i=1;i<=n;i++)cout<<son[i]<<" ";
	//cout<<endl;
	while(m--){
		//for(int i=1;i<=n;i++)
		//cout<<pos[i]<<" "<<query(1,pos[i],pos[i])<<endl;
		//cout<<query(1,pos[1],pos[1])<<endl;
		//cout<<endl;
		int e,f,g,h;
		e=read();
		if(e==1){
			f=read();g=read();h=read();
			pathadd(f,g,h);
		}
		if(e==2){
			f=read();g=read();
			printf("%d\n",pathquery(f,g));
		}
		if(e==3){
			f=read();g=read();
			add1(1,pos[f],pos_ed[f],g);
		}
		if(e==4){
			f=read();
			printf("%d\n",query(1,pos[f],pos_ed[f]));
		}
		
	} 
	return 0;
}

顺便粘一组数据~~

输入
8 10 2 448348
458 718 447 857 633 264 238 944 
1 2
2 3
3 4
6 2
1 5
5 7
8 6
3 7 611
4 6
3 1 267
3 2 111
1 6 3 153
3 7 673
4 8
2 6 1
4 7
3 4 228
输出
1208
1055
2346
1900
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值