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

树链剖分

时间:2022-09-18 21:15:50浏览次数:72  
标签:剖分 NowSum int long 树链 重链

解决树上的很多问题。比如子树操作,链操作等等,但是不能处理动态问题(处理动态树问题大都要用 LCT)。

树链剖分同可持久化线段树一样,只是一个工具,难点都在维护的东西上。像什么树上 DDP,就是用树链剖分维护,难点全在列矩阵上。(用完全平衡二叉树可以更快,但没必要)

树链剖分就是利用了重链剖分剖出来之后每个点到根节点都最多只会跳 log 条重链。而 dfs 序按照先重儿子再其它儿子的顺序排,就可以让所有重链的编号连续。而连续,就可以在线段树上维护。(平衡树也可以。平衡树可以翻转区间,不过树链剖分都是静止的,翻转也用不着。)

重链剖分就是哪个儿子的子树内节点比较多就认那个儿子当重儿子,其它儿子就是它们的重链的顶端。

对于链上操作,链的两个端点,哪个的当前重链的深度深就把这条链修改,然后跳到重链顶端的父亲节点,再继续,直到它们在同一条重链上。然后在最后这 2 个点的区间内再做一次修改。

对于子树操作,记录每个点的子树遍历完之后出来时的 dfs 序编号,这个就是当前子树在线段树上的区间末尾(区间起点是这个点的 dfs 序)。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+50;
int N,M,Root,P;
long long Val[MAXN];
struct Edge
{
	int x,y,Next;
}e[MAXN<<1];
int elast[MAXN],tot;
void Add(int x,int y)
{
	tot++;
	e[tot].x=x;
	e[tot].y=y;
	e[tot].Next=elast[x];
	elast[x]=tot;
}
int Size[MAXN],Son[MAXN],Top[MAXN],father[MAXN],depth[MAXN];
int Id[MAXN],CntId,Back[MAXN];
long long Length[MAXN];
void dfs1(int u,int fa)
{
	depth[u]=depth[fa]+1;
	Size[u]=1;
	father[u]=fa;
	for(int i=elast[u];i;i=e[i].Next)
	{
		int v=e[i].y;
		if(v==fa)
		continue;
		dfs1(v,u);
		Size[u]+=Size[v];
		if(Size[v]>Size[Son[u]])
		{
			Son[u]=v;
		}
	}
}
int In[MAXN],Out[MAXN];
void dfs2(int u,int fa,int zuzong)
{
	Top[u]=zuzong;
	Id[u]=++CntId; 
	In[u]=CntId;
	Back[CntId]=u;
	if(Son[u])
	{
		dfs2(Son[u],u,zuzong);
	}
	for(int i=elast[u];i;i=e[i].Next)
	{
		int v=e[i].y;
		if(v==fa||v==Son[u])
		continue;
		dfs2(v,u,v);
	}
	Out[u]=CntId;
}
long long Sum[MAXN<<2],Lazy[MAXN<<2];
void PushDown(int u)
{
	Lazy[u<<1]=(Lazy[u<<1]+Lazy[u])%P;
	Lazy[u<<1|1]=(Lazy[u<<1|1]+Lazy[u])%P; 
	Sum[u<<1]=(Sum[u<<1]+Lazy[u]*Length[u<<1])%P;
	Sum[u<<1|1]=(Sum[u<<1|1]+Lazy[u]*Length[u<<1|1])%P;
	Lazy[u]=0;
}
void Build(int u,int l,int r)
{
	if(l==r)
	{
		Sum[u]=Val[Back[l]];
		Length[u]=1;
		return;
	}
	int Mid=l+r>>1;
	Build(u<<1,l,Mid);
	Build(u<<1|1,Mid+1,r);
	Sum[u]=(Sum[u<<1]+Sum[u<<1|1])%P;
	Length[u]=Length[u<<1]+Length[u<<1|1];
}
void Modify(int u,int l,int r,int x,int y,long long k)
{
	if(x<=l&&r<=y)
	{
		Sum[u]=(Sum[u]+Length[u]*k)%P;
		Lazy[u]=(Lazy[u]+k)%P;
		return;
	}
	if(Lazy[u])
	PushDown(u);
	int Mid=l+r>>1;
	if(x<=Mid&&y>=l)
	{
		Modify(u<<1,l,Mid,x,y,k);
	}
	if(x<=r&&y>=Mid+1)
	{
		Modify(u<<1|1,Mid+1,r,x,y,k);
	}
	Sum[u]=(Sum[u<<1]+Sum[u<<1|1])%P;
}
long long Query(int u,int l,int r,int x,int y)
{
	if(x<=l&&r<=y)
	{
		return Sum[u];
	}
	if(Lazy[u])
	PushDown(u);
	int Mid=l+r>>1;
	long long NowSum=0;
	if(x<=Mid&&y>=l)
	NowSum=(NowSum+Query(u<<1,l,Mid,x,y))%P;
	if(x<=r&&y>=Mid+1)
	NowSum=(NowSum+Query(u<<1|1,Mid+1,r,x,y))%P;
	return NowSum;
}
int main()
{
	scanf("%d%d%d%d",&N,&M,&Root,&P);
	for(int i=1;i<=N;i++)
	{
		scanf("%lld",&Val[i]);	
	}
	for(int i=1;i<N;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		Add(x,y);
		Add(y,x);
	}
	dfs1(Root,0);
	dfs2(Root,0,Root);
	Build(1,1,N);
	while(M--)
	{
		int op,x,y;
		long long z;
		scanf("%d%d",&op,&x);
		if(op==1)
		{
			scanf("%d%lld",&y,&z);
			while(Top[x]!=Top[y])
			{
				if(depth[Top[x]]<depth[Top[y]])
				swap(x,y);
				Modify(1,1,N,In[Top[x]],In[x],z);
				x=father[Top[x]];
			}
			if(depth[x]<depth[y])
			swap(x,y);
			Modify(1,1,N,In[y],In[x],z);
		}
		if(op==2)
		{
			scanf("%d",&y);
			long long NowSum=0;
			while(Top[x]!=Top[y])
			{
				if(depth[Top[x]]<depth[Top[y]])
				swap(x,y);
				NowSum=(NowSum+Query(1,1,N,In[Top[x]],In[x]));
				x=father[Top[x]];
			}
			if(depth[x]<depth[y])
			swap(x,y);
			NowSum=(NowSum+Query(1,1,N,In[y],In[x]))%P;
			printf("%lld\n",NowSum);
		}
		if(op==3)
		{
			scanf("%lld",&z);
			Modify(1,1,N,In[x],Out[x],z); 
		}
		if(op==4)
		{
			printf("%lld\n",Query(1,1,N,In[x],Out[x]));
		}
	}
}

今年二月我写过一次模板,一直 WA20 分,后来才调对。今天写,又 WA20 分,跟之前那次一模一样。错误原因竟然是——区间加一个数区间和数组真就只加了这个数,没乘区间长度。下次一定不要再犯这样的错误了。

标签:剖分,NowSum,int,long,树链,重链
From: https://www.cnblogs.com/0htoAi/p/16705777.html

相关文章

  • 树链剖分
    树链剖分的主要支持以下操作:将树结点$x$到$y$的最短路径上所有结点加权查询树结点$x$到$y$的最短路径上所有结点的权值总和将以$x$为根的子树内所有结点加权查询以$x$......
  • Static Query on Tree (述链剖分+线段树)(2022杭电多校)
    题意:给定一棵树,nn 个结点。根为 11,所有的结点只能走向其父亲结点。有 qq 次询问,每次询问给出 33 个结点集合 A,B,CA,B,C。问树上有多少点满足如下条件:该点可以......
  • 树链剖分,树剖
    树剖是把一棵树拆成一堆链,\(O(logn)\)地跳链,用一些数据结构维护每条链,从而实现增加1k代码而降低复杂度到\(O(log^2n)\)的效果。树链剖分大概分三种:长链剖分,实链剖分和重链......
  • 长链剖分以及对剖分的理解
    https://www.cnblogs.com/maoyiting/p/14178833.html#/cnblog/works/article/14178833目前接触到的重链剖分,长链剖分,实链剖分里面都有一些共同的性质吧!比如,每个点仅存在......
  • CF609E Minimum spanning tree for each edge 【最小生成树+树链剖分】
    CF609EMinimumspanningtreeforeachedge题目描述给你\(n\)个点,\(m\)条边,如果对于一个最小生成树中要求必须包括第\(i(1\lei\lem)\)条边,那么最小生成树的......
  • 树链剖分
    一.写在文前前置芝士:线段树,另外可能学了LCA会更好理解一点树链剖分的板子就是基于线段树哒!不会的读者先去学习它再来看下面的部分叭!qwq先阅读下板子题叭!二.一些有用的......
  • 基础长链剖分
    基础长链剖分基本上整个互联网上长链剖分都是使用CF1009F和树上\(k\)级祖先两题。本篇也无法避免qwq,因为这两题确实经典。定义定义重儿子表示其子节点中子树深度......
  • LCA 相关 && 树链剖分
    LCA基本定义:最近公共祖先简称LCA(LowestCommonAncestor)。两个结点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。简单来讲,就是两个点到根的路径上,深度最深......