树链剖分基础

基本定义

把一棵树分成很多条链,然后利用数据结构(线段树、树状数组等)来维护这些链。

作用

树链剖分用于树路径信息维护。

剖分方式 

  1. 随便剖分

  2. 随机剖分

  3. 轻重链剖分(本文)

轻重链剖分的基本概念

重结点:子树结点数目最多的结点;

轻节点:父亲节点中除了重结点以外的结点;

重边:父亲结点和重结点连成的边;

轻边:父亲节点和轻节点连成的边;

重链:由多条重边连接而成的路径;

轻链:由多条轻边连接而成的路径;

如图:加粗的边为重边,红点为每条重链的顶端节点(后称top)。

基本性质

性质1轻边(U,V)size(V)<=size(U)/2

如果size(V)>size(U)/2的话,那么VUsize值最大的儿子节点,则(U,V)为重边。

性质2从根到某一点的路径上,不超过O(logN)条轻边不超过O(logN)条重路径。

根到某一点的路径上,轻边最多的情况,就是所有的边都是轻边,那么不可能超过O(logN)条的,一个点到根节点的路径上,是一条重路径与一条轻边交替出现,所以重路径的条数最多和轻边条数一样,也就是不超过O(logN)条。

重链剖分的实现

重链剖分的过程为2DFS

需要用到的数组:

siz[u]//用来保存以u为根的子树结点个数
top[u]//用来保存当前结点u所在链的顶端节点
son[u]//用来保存当前结点u的重儿子编号
dep[u]//用来保存当前结点x的深度
fa[u]//用来保存当前结点x的父亲编号
id[u]//用来保存树中每个结点剖分后的新编号,即DFS序编号
rnk[pos]//用来保存线段树中位置为pos的结点在原树上的编号
//tid[]和rnk[]互为反函数

第一次dfs:找重边

inline void dfs1(int u,int fa,int d)
{
    dep[u]=d,son[u]=0,siz[u]=1,fat[u]=fa;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v!=fa)
        {
            dfs1(v,u,d+1);
            siz[u]+=siz[v];
            if(siz[son[u]]<siz[v])//找siz最大的子节点
				son[u]=v;
        }
    }
}

第二次dfs:连重边成重链

 

inline void dfs2(int u,int tp)
{
    top[u]=tp;//当前重链的起始位置
	id[u]=++tot;//分配新编号
    if(son[u])//重儿子继续连在当前重链上
		dfs2(son[u],top[u]);
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v!=fat[u]&&v!=son[u])
			dfs2(v,v);//轻儿子自己作为新的重链端点
    }
}

 

维护重链

剖分完之后,每条重链就相当于一段区间用数据结构去维护。

把所有的重链首尾相接,放到同一个数据结构上,然后维护这一个整体即可。

至于选择什么样的数据结构去维护,要根据题目的需要。

因为重链是线性序列,所以用一棵线段树维护最常见。

 

修改操作

    树上单点修改操作

根据id[u]得到原树上的结点u在线段树上的位置,直接在线段树中修改。

    树上沿路径修改操作

整体修改点 u和点v的路径上的权值 。

A.如果UV在同一条重链上

                    直接用数据结构修改id[u]id[v]间的值。

B.如果UV不在同一条重链上

                   一边进行修改,一边将uv往同一条重链上靠,然后就变成了A的情况。

        a.fa[top[u]]v在同一条重链上。

        修改点utop[u]间的各权值,然后u跳至fa[top[u],就变成了I的情况

        b.u向上经过若干条重链和轻边与v在同一条重链上。

    

        不断地修改当前utop[u]间的各权值,再将u跳至fa[top[u]]直到uv在同一条重链。

       c.uv都是向上经过若干条重链和轻边,到达同一条重链

        

       每次在点u和点v中,选择dep[top[x]]较大的点x,修改xtop[x]间的各权值,再跳至fa[top[x]]直到点u和点v同一条重链

    总结

情况ab是情况c的比较特殊的2

A也只是B的特殊情况。

所以,这一操作只要用一个过程。

代码:

inline void M(int v,int u,int v1)
{
    int t1=top[v],t2=top[u];
    while(t1!=t2)
    {
        if(dep[t1]<dep[t2])//每次处理深度大的
            swap(t1,t2),swap(v,u);
        modify(1,id[t1],id[v],v1);//线段树操作
        v=fat[t1],t1=top[v];//往上传
    }
    if(dep[v]>dep[u]) 
		swap(v,u);
    modify(1,id[v],id[u],v1);
}

 

查询操作

和修改操作一模一样(区别在线段树里)

inline void Q(int v,int u,int v1)
{
    int t1=top[v],t2=top[u];
    while(t1!=t2)
    {
        if(dep[t1]<dep[t2])
            swap(t1,t2),swap(v,u);
        query(1,id[t1],id[v],v1);
        v=fat[t1],t1=top[v];
    }
    if(dep[v]>dep[u]) 
		swap(v,u);
    query(1,id[v],id[u],v1);
}

 

例题

树的统计Count

纯模板,没什么好说的,主要看个格式

代码:

#include<cstdio>
#include<algorithm>
#include<climits>
#include<vector>
using namespace std;
#define N 1000005
int dep[N],siz[N],son[N],fat[N],id[N],top[N];
int tot,n,m,c[N];
struct node
{
    int l,r,sum,mx;
}t[N*4];
vector<int>g[N];
inline void dfs1(int u,int fa,int d)
{
    dep[u]=d,son[u]=0,siz[u]=1,fat[u]=fa;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v!=fa)
        {
            dfs1(v,u,d+1);
            siz[u]+=siz[v];
            if(siz[son[u]]<siz[v])
				son[u]=v;
        }
    }
}
inline void dfs2(int u,int tp)
{
    top[u]=tp;
	id[u]=++tot;
    if(son[u])
		dfs2(son[u],top[u]);
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v!=fat[u]&&v!=son[u])
			dfs2(v,v);
    }
}
inline void push_up(int i)
{
    t[i].sum=t[i<<1].sum+t[i<<1|1].sum;
	t[i].mx=max(t[i<<1].mx,t[i<<1|1].mx);
}
inline void build(int l,int r,int i)
{
    t[i].l=l,t[i].r=r,t[i].sum=t[i].mx=0;
    if(l==r)
        return;
    int mid=(l+r)>>1;
    build(l,mid,i<<1);
    build(mid+1,r,i<<1|1);
    push_up(i);
}
inline void modify(int i,int x,int v)
{
    if(t[i].l==t[i].r)
    {
        t[i].sum=t[i].mx=v;
        return;
    }
    int mid=(t[i].l+t[i].r)>>1;
    if(x<=mid)
		modify(i<<1,x,v);
    else 
		modify(i<<1|1,x,v);
    push_up(i);
}
inline int q1(int l,int r,int i)
{
    if(t[i].l==l&&r==t[i].r) 
		return t[i].mx;
	int mid=(t[i].l+t[i].r)>>1;
    if(r<=mid)
		return q1(l,r,i<<1);
    else if(l>mid)
		return q1(l,r,i<<1|1);
    else 
		return max(q1(l,mid,i<<1),q1(mid+1,r,i<<1|1));
}
inline int Q1(int v,int u)
{
    int t1=top[v],t2=top[u];
    int ans=-INT_MAX;
    while(t1!=t2)
    {
        if(dep[t1]<dep[t2])
            swap(t1,t2),swap(v,u);
        ans=max(ans,q1(id[t1],id[v],1));
        v=fat[t1],t1=top[v];
    }
    if(dep[v]>dep[u]) 
		swap(v,u);
    ans=max(ans,q1(id[v],id[u],1));
    return ans;
}
inline int q2(int l,int r,int i)
{
    if(t[i].l==l&&r==t[i].r) 
		return t[i].sum;
	int mid=(t[i].l+t[i].r)>>1;
    if(r<=mid)
		return q2(l,r,i<<1);
    else if(l>mid)
		return q2(l,r,i<<1|1);
    else 
		return q2(l,mid,i<<1)+q2(mid+1,r,i<<1|1);
}
inline int Q2(int v,int u)
{
    int t1=top[v],t2=top[u];
    int ans=0;
    while(t1!=t2)
    {
        if(dep[t1]<dep[t2])
            swap(t1,t2),swap(v,u);
        ans+=q2(id[t1],id[v],1);
        v=fat[t1],t1=top[v];
    }
    if(dep[v]>dep[u]) 
		swap(v,u);
    ans+=q2(id[v],id[u],1);
    return ans;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		g[x].push_back(y);
		g[y].push_back(x);
	}
	dfs1(1,0,1);
	dfs2(1,1);
	build(1,tot,1);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		modify(1,id[i],x);
	}
	int Q;
	scanf("%d",&Q);
	while(Q--)
	{
		char op[15];
		int x,y;
		scanf("%s%d%d",op,&x,&y);
		if(op[1]=='H')
			modify(1,id[x],y);
		else if(op[1]=='M')
			printf("%d\n",Q1(x,y));
		else 
			printf("%d\n",Q2(x,y));
	}
	return 0;
}

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_37657307/article/details/81428088