树链剖分总结

树链剖分总结(讲解)

前言

我们在进行树上操作时往往会遇见一下操作:

  •   u \ u  u节点到   v \ v  v节点的最短路径上所有节点全部加上一个数   k \ k  k
  •   u \ u  u节点到   v \ v  v几点的最短路径上所有点权之和。
  • 求以   u \ u  u为根的子树的点权之和。

若单独处理这些问题是容易的(差分,LCA,DFS预处理)。但这些操作混到一起就不能用这些方法解决了,所以我们就引入树链剖分

树链剖分的相关约定

  • 重儿子:父亲节点的所有儿子中子树结点数目最多的结点。
  • 轻儿子:父亲节点中除了重儿子以外的儿子。
  • 重边:父亲结点和重儿子连成的边。
  • 轻边:父亲节点和轻儿子连成的边。
  • 重链:由多条重边连接而成的路径。
  • 轻链:由多条轻边连接而成的路径。

注意,若一个节点有多个重儿子则任选其一。
儿子

红色为重儿子,黄色为轻儿子,3号节点的重儿子也可以是6或4。

重链
其中红色为重链。橘色为轻链或重链的顶端。

相关声明

声明定义
fa[u]结点   u \ u  u的父亲节点。
deep[u]结点   u \ u  u的深度值。
siz[u]   u \ u  u为根的子树大小。
son[u]节点   u \ u  u的重儿子。
yuan[u]当前dfs标号在树中所对应的节点。
top[u]当前节点所在链的顶端节点。
id[u]树中每个节点剖分以后的新编号,即dfs序。

其中dfs的求法应先遍历重链,保证重链的dfs序是连续的
在这里插入图片描述像这样。

重链内容重链dfs序
2 , 3 , 52 , 3 , 4
9 , 108 , 9

树链剖分的主要思路

0.声明

#include<bits/stdc++.h>
using namespace std;
int n,m,hea[100100],len=0,ide=0,siz[100100],son[100100],top[100100],deep[100100],id[100100],fa[100100],yuan[100100];
struct nobe
{
	int ne,to;
}e[200100];
inline int read()
{
    int s=0,w=0;
    char ch=0;
    while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    while(isdigit(ch)){s=(s<<3)+(s<<1)+(ch^0x30);ch=getchar();}
    return w ? -s : s;
}

1.求出重儿子数组和子树大小(son[u],siz[u])

这个是容易理解的,dfs即可

inline void dfs1(int x,int f)
{
	siz[x]=1;
	fa[x]=f;
	son[x]=0;
	deep[x]=deep[f]+1;
	int i=hea[x];
	while(i)
	{
		int y=e[i].to;
		if(y!=f)
		{
			dfs1(y,x);
        	if(siz[y]>siz[son[x]]) son[x]=y;
        	siz[x]+=siz[y];
		}
		i=e[i].ne;
	}
}

2.求出链顶和dfs序(id[x],top[x],yuan[x])

注意,应优先dfs重儿子。

inline void dfs2(int x,int topp)
{
	id[x]=++ide;
	top[x]=topp;
	yuan[ide]=x;
	if(!son[x]) return ;
	dfs2(son[x],topp);
	int i=hea[x];
	while(i)
	{
		int y=e[i].to;
		if((y!=fa[x])&&(y!=son[x])) dfs2(y,y);
		i=e[i].ne;
	}
}

这是树链剖分的所有预处理。
我们的dfs序保证了每一条链上的点是连续的。
连续指id[x]==id[fa[x]]+1,即一个节点的dfs序比它父亲节点的dfs序大1。
那么我们就可以用数据结构来维护一条链的信息。
厉害的同学可能已经懂了。反正我不懂。

对于修改操作。

  •   u \ u  u节点到   v \ v  v节点的最短路径上所有节点全部加上一个数。

我们先从   u \ u  u节点和   v \ v  v节点向上跳。
若两点不在一条链上时,每次从重链(轻链)上的一点跳到链顶(x=top[x]),再处理这一条链的贡献。
再一条链上就特判一下。
复杂度O(   l o g 2 n \ log^{2}n  log2n)

inline void treeupdate()
{
    int x,y,k;
	x=read();
	y=read();
	k=read();//读入x,y,和修改的值。
    while(top[x]!=top[y])//不在同一链上、
	{
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];//跳出这条链。
    }
	if(deep[x]>deep[y]) swap(x,y);//在同一链上。
    update(1,1,n,id[x],id[y],k);
}

路径上的查询

  •   u \ u  u节点到   v \ v  v几点的最短路径上所有点权之和。

与修改类似,跳一跳即可。
复杂度O(   l o g 2 n \ log^{2}n  log2n)

inline int treepathquery()
{
    int x,y,k,ans=0;
	x=read();
	y=read();//读入x,y。
    while(top[x]!=top[y])//不在同一链上、
	{
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        ans+=query(1,1,n,id[top[x]],id[x]);
        x=fa[top[x]];//跳出这条链。
    }
	if(deep[x]>deep[y]) swap(x,y);//在同一链上。
	ans+=query(1,1,n,id[x],id[y]);
	return ans;
}

子树查询

  • 求以   u \ u  u为根的子树的点权之和。

根据dfs序的定义,我们可以知道已知节点   u \ u  u其子树大小为   s i z e \ size  size,那么它以及它的子树dfs序一定为   i d u − − i d u + s i z e − 1 \ id_{u}--id_{u+size-1}  iduidu+size1
挺好理解的,不明白的自己画个小图模拟一下就是显然的了。
那就随便写写就有代码。
复杂度O(   l o g n \ logn  logn)

inline void treequery()
{
    int x;
	x=read();
    printf("%lld\n",query(1,1,n,id[x],id[x]+siz[x]-1));//简单粗暴的查询。
}

具体大概就这样了。

总结

树链剖分可进行很多操作,远不止上面说的那么少。学好树链剖分,可瞎搞A题。
相信大家有举一反三的能力。
几道树链题。(树链剖分一堆码农题。)(这些都不是码农题。)

码农题。

前两道是板子,最后一道不算是。
最后祝大家用好树链剖分,熟练剖分。









魔法树AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,hea[100100],len=0,ide=0,siz[100100],son[100100],top[100100],deep[100100],id[100100],fa[100100];
struct nobe
{
	int ne,to;
}e[200100];
struct tree
{
	long long sum,lazy;
	int siz;
}t[400100];
inline int read()
{
    int s=0,w=0;
    char ch=0;
    while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    while(isdigit(ch)){s=(s<<3)+(s<<1)+(ch^0x30);ch=getchar();}
    return w ? -s : s;
}
//---------------------------------------------------------dfs---------------------------------------------------------
inline void adde(int a,int b)
{
	e[++len].to=b;
	e[len].ne=hea[a];
	hea[a]=len;
}
inline void dfs1(int x,int f)
{
	siz[x]=1;
	fa[x]=f;
	son[x]=0;
	deep[x]=deep[f]+1;
	int i=hea[x];
	while(i)
	{
		int y=e[i].to;
		if(y!=f)
		{
			dfs1(y,x);
        	if(siz[y]>siz[son[x]]) son[x]=y;
        	siz[x]+=siz[y];
		}
		i=e[i].ne;
	}
}
inline void dfs2(int x,int topp)
{
	id[x]=++ide;
	top[x]=topp;
	if(!son[x]) return ;
	dfs2(son[x],topp);
	int i=hea[x];
	while(i)
	{
		int y=e[i].to;
		if((y!=fa[x])&&(y!=son[x])) dfs2(y,y);
		i=e[i].ne;
	}
}
//---------------------------------------------------------dfs---------------------------------------------------------
//-------------------------------------------------------sigtree-------------------------------------------------------
inline void pushup(int k)
{
	t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
inline void pushdown(int k)
{
	t[k<<1].sum+=t[k<<1].siz*t[k].lazy,
    t[k<<1|1].sum+=t[k<<1|1].siz*t[k].lazy,
    t[k<<1].lazy+=t[k].lazy,
    t[k<<1|1].lazy+=t[k].lazy,
    t[k].lazy=0;
}
inline void build(int k,int l,int r)
{
	t[k].siz=r-l+1;
    if(l==r)
	{
        t[k].sum=0;
        return ;
    }
	int mid=(l+r)>>1;
    build(k<<1,l,mid);
	build(k<<1|1,mid+1,r),
    pushup(k);
}
inline void update(int k,int l,int r,int ll,int rr,long long add)
{
	if((l>=ll)&&(r<=rr))
	{
		t[k].sum+=t[k].siz*add;
		t[k].lazy+=add;
		return ;
	}
	if(t[k].lazy) pushdown(k);
	int mid=(l+r)>>1;
	if(rr<=mid) update(k<<1,l,mid,ll,rr,add);
    else if(ll>mid) update(k<<1|1,mid+1,r,ll,rr,add);
    else update(k<<1,l,mid,ll,rr,add),update(k<<1|1,mid+1,r,ll,rr,add);
    pushup(k); 
}
inline long long query(int k,int l,int r,int ll,int rr)
{
	if(l>r) return 0;
	if((l>=ll)&&(r<=rr)) return t[k].sum;
	if(t[k].lazy) pushdown(k);
	int mid=(l+r)>>1;
	if(rr<=mid) return query(k<<1,l,mid,ll,rr);
    else if(ll>mid) return query(k<<1|1,mid+1,r,ll,rr);
    else return query(k<<1,l,mid,ll,rr)+query(k<<1|1,mid+1,r,ll,rr);
}
//-------------------------------------------------------sigtree-------------------------------------------------------
//--------------------------------------------------------treec--------------------------------------------------------
inline void treeupdate()
{
    int x,y;
	long long k;
	x=read();
	y=read();
	++x;
	++y;
	k=read();
    while(top[x]!=top[y])
	{
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        update(1,1,n,id[top[x]],id[x],k);
        x=fa[top[x]];
    }
	if(deep[x]>deep[y]) swap(x,y);
    update(1,1,n,id[x],id[y],k);
}
inline void treequery()
{
    int x;
	x=read();
	++x;
    printf("%lld\n",query(1,1,n,id[x],id[x]+siz[x]-1));
}
//--------------------------------------------------------treec--------------------------------------------------------
signed main()
{
	memset(t,0,sizeof(t));
	memset(siz,0,sizeof(siz));
	memset(hea,0,sizeof(hea));
	n=read();
	int i=1;
	while(i<n)
	{
		int u,v;
		u=read();
		v=read();
		++u;
		++v;
		adde(u,v);
		adde(v,u);
		++i;
	}
	fa[1]=0;
	deep[0]=0;
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	m=read();
	while(m--)
	{
		char op=getchar();
		while((op==' ')||(op=='\n')) op=getchar();
        if(op=='A') treeupdate();
        else treequery();
	}
	return 0;
}

软件包管理器AC代码

#include<bits/stdc++.h>
using namespace std;
int n,m,len=0,hea[100100];
int deep[100100],fa[100100],top[100100],son[100100],id[100100],siz[100100],yuan[100100],ide=0;
struct nobe
{
	int ne,to;
}e[100100];
struct tree
{
    int sum,siz,fl;
}t[400100];
inline int read()
{
    int s=0,w=0;
    char ch=0;
    while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
    while(isdigit(ch)){s=(s<<3)+(s<<1)+(ch^0x30);ch=getchar();}
    return w ? -s : s;
}
//----------------------------------------------------dfs----------------------------------------------------
inline void adde(int u,int v)
{
	e[++len].to=v;
	e[len].ne=hea[u];
	hea[u]=len;
}
inline void dfs1(int x,int f)
{
	fa[x]=f;
	deep[x]=deep[f]+1;
	son[x]=0;
	siz[x]=1;
	int i=hea[x];
	while(i)
	{
		int y=e[i].to;
		if(y!=f)
		{
			dfs1(y,x);
			siz[x]+=siz[y];
			if(siz[son[x]]<siz[y]) son[x]=y;
		}
		i=e[i].ne;
	}
}
inline void dfs2(int x,int topp)
{
	top[x]=topp;
	++ide;
	id[x]=ide;
	yuan[ide]=x;
	if(!son[x]) return ;
	dfs2(son[x],topp);
	int i=hea[x];
	while(i)
	{
		int y=e[i].to;
		if((y!=fa[x])&&(y!=son[x])) dfs2(y,y);
		i=e[i].ne;
	}
}
//----------------------------------------------------dfs----------------------------------------------------
//--------------------------------------------------sigtree--------------------------------------------------
inline void pushdata(int k)
{
	t[k<<1].sum=t[k<<1].siz*t[k].fl;
    t[k<<1|1].sum=t[k<<1|1].siz*t[k].fl;
    t[k<<1].fl=t[k<<1|1].fl=t[k].fl;
    t[k].fl=-1;
}
inline void build(int k,int l,int r)
{
	t[k].siz=r-l+1;
	t[k].sum=0;
	t[k].fl=-1;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
void update(int k,int l,int r,int ll,int rr,int val)
{
    if((r<ll)||(l>rr)) return ;
    if((r<=rr)&&(l>=ll))
    {
        t[k].sum=t[k].siz*val;
        t[k].fl=val;
        return ;
    }
    int mid=(l+r)>>1;
    if(t[k].fl!=-1) pushdata(k);
    update(k<<1,l,mid,ll,rr,val);
	update(k<<1|1,mid+1,r,ll,rr,val);
    t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
inline int query(int k,int l,int r,int ll,int rr)
{
	if((r<ll)||(l>rr)) return 0;
	if((r<=rr)&&(l>=ll)) return t[k].sum;
    if(t[k].fl!=-1) pushdata(k);
    int mid=(l+r)>>1;
    return query(k<<1,l,mid,ll,rr)+query(k<<1|1,mid+1,r,ll,rr);
}
//--------------------------------------------------sigtree--------------------------------------------------
void treechange(int u,int v,int val)
{
    while(top[u]!=top[v])
    {
        if(deep[top[u]]<deep[top[v]]) swap(u,v);
        update(1,1,n,id[top[u]],id[u],val);
        u=fa[top[u]];
    }
    if(deep[u]>deep[v]) swap(u,v);
    update(1,1,n,id[u],id[v],val);
}
int main()
{
	memset(t,0,sizeof(t));
	memset(hea,0,sizeof(hea));
	n=read();
	int i=2;
	while(i<=n)
	{
		int a;
		a=read();
		++a;
		adde(a,i);
		++i;
	}
	fa[1]=0;
	deep[0]=0;
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	m=read();
	char s[10];
	while(m--)
	{
		scanf("%s",s);
        int x;
		x=read();
		++x;
        int last=t[1].sum;
        if(s[0]=='i')
        {
            treechange(1,x,1);
            int now=t[1].sum;
            printf("%d\n",abs(now-last));

        }
        if(s[0]=='u')
        {
            update(1,1,n,id[x],id[x]+siz[x]-1,0);
            int now=t[1].sum;
            printf("%d\n",abs(last-now));
        }
	}
	return 0;
}

RPRPRP

/*
1996年:东方灵异传(TOH1)
1997年:东方封魔录(TOH2)
1997年:东方梦时空(TOH3)
1998年:东方幻想乡(TOH4)
1998年:东方怪绮谈(TOH5)
2002年:东方红魔乡(TOH6)
2003年:东方妖妖梦(TOH7)
2004年:东方萃梦想(TOH7.5)
2004年:东方永夜抄(TOH8)
2005年:东方花映冢(TOH9)
2005年:东方文花帖(TOH9.5)
2007年:东方风神录(TOH10)
2008年:东方绯想天(TOH10.5)
2008年:东方地灵殿(TOH11)
2009年:东方星莲船(TOH12)
2009年:东方非想天则(TOH12.3)
2010年:东方文花帖DS(TOH12.5)
2010年:东方三月精(TOH12.8)
2011年:东方神灵庙(TOH13)
2013年:东方心绮楼(TOH13.5)
2013年:东方辉针城(TOH14)
2014年:弹幕天邪鬼(TOH14.3)
2014年:东方深秘录(TOH14.5)
2015年:东方绀珠传(TOH15)
2017年:东方凭依华(TOH15.5)
2017年:东方天空璋(TOH16)
*/
//RP++
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值