模板总结——树链剖分

任务

给定一颗树,要求对树上的路径(u,v)进行高效操作。
(1)更新路径(u,v)上所有点的权值,单点查询树上某点的权值
(2)更新结点u的权值,查询路径(u,v)上的权值和
(3)更新路径(u,v)上的权值,查询路径(u,v)上的权值和
(4)更新结点u的权值,查询路径(u,v)上的LCIS
(5)更新结点u到根结点路径上的权值,更新u子树的权值,查询u到根结点权值,查询u子树权值

树链剖分

树链剖分就是将树上的结点重新编一个号,形成一个连续的区间,通过维护这个区间从而达到维护树的目的。
因为树链剖分多是对路径的操作,所以这个重新编号工作要使得一个路径可以切分成几个连续区间。
所以引入重链的概念,要求每一条重链上的结点都是连续的,每一个子树上的结点都是连续的。

相关概念

重儿子:u的重儿子是v当且仅当v子树的结点总数(包括v)是u的所有子树中结点总数最大的
轻儿子:每个非叶子结点只有一个重儿子,其他是轻儿子
重链:由重儿子向父结点连重边,一段最长连续重边叫做重链

第一次dfs

主要目的是确定重儿子。
要确定重儿子son,需要确定子树结点总数sz.
确定深度dep和父结点fa是因为对路径操作需要。

void dfs1(int u,int f,int deep)//第一遍dfs确定结点深度dep和重儿子son以及为了确定重儿子而所需要知道的信息sz
{
    dep[u]=deep;
    fa[u]=f;//确定父结点是为了下面对路径进行查询和修改时结点向上爬时所用
    sz[u]=1;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==f) continue;
        dfs1(v,u,deep+1);
        sz[u]+=sz[v];
        if(son[u]==-1 || sz[v]>sz[son[u]])
        {
            son[u]=v;
        }
    }
}

第二次dfs

主要目的是将树的结点与区间的点对应起来。
每条重链是一个连续区间,所以需要确定重链。
确定重链的顶端结点top是因为对路径操作需要,一爬爬一条路径,速度快。
通过两次dfs就成功地将树与一段区间对应起来了,接下来用树状数组或线段树去维护区间即可。

void dfs2(int u,int topf)//第二遍dfs按重儿子走,确定每一条树链
{
    id[u]=++cnt;//确定每个结点对应在树状数组中的结点
    pos[cnt]=u;//由树状数组中的结点对应树中的结点
    top[u]=topf;//确定结点的链顶端结点
    if(son[u]==-1) return;//递归到叶子结点
    dfs2(son[u],topf);//先递归重儿子,保证一条链上的结点在树状数组中的编号是连续的
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa[u] || v==son[u]) continue;
        dfs2(v,v);//对于每一个轻儿子都有一条从它自己开始的链
    }
}

对路径的操作

对路径的查询与更新操作类似。

void update_path(int x,int y,int k)//将路径x-y上的所有结点的值都加k
{
    while(top[x]!=top[y])//不在一条链上,让链顶端结点更深的结点向上爬
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);//将x改为链顶端深度更深的那个点
        //更新路径top[x]-->x
        //对应树状数组的区间为[l,r]
        int l=id[top[x]],r=id[x];
        add(l,k);
        add(r+1,-k);
        x=fa[top[x]];//爬到链顶端结点的父结点
    }
    //直到两个点在一条链上
    if(dep[x]>dep[y]) swap(x,y);//将x改为本身深度更浅的结点
    //更新路径x-->y
    //结点x和y对应在树状数组中的结点编号
    int l=id[x],r=id[y];
    add(l,k);
    add(r+1,-k);
}

模板

//HDU3966
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <iostream>
using namespace std;
const int maxn=5e4+100;

//树状数组
int tree[maxn],a[maxn];
int lowbit(int i){return i&(-i);}
void add(int i,int v)
{
    while(i<maxn)
    {
        tree[i]+=v;
        i+=lowbit(i);
    }
}
int sum(int i)
{
    int ans=0;
    while(i)
    {
        ans+=tree[i];
        i-=lowbit(i);
    }
    return ans;
}

//树链剖分
vector<int> g[maxn];//存图
int cnt;//树状数组中的元素的编号
int dep[maxn];//在树中的深度
int id[maxn];//id[u]表示u结点对应在树状数组中的编号
int pos[maxn];//pos[i]表示树状数组第i个元素对应的树中结点
int top[maxn];//该结点所在链的顶端的结点
int son[maxn];//该结点的重儿子
int sz[maxn];//以该结点为根的树所含结点总数
int fa[maxn];//该结点的父结点
void dfs1(int u,int f,int deep)//第一遍dfs确定结点深度dep和重儿子son以及为了确定重儿子而所需要知道的信息sz
{
    dep[u]=deep;
    fa[u]=f;//确定父结点是为了下面对路径进行查询和修改时结点向上爬时所用
    sz[u]=1;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==f) continue;
        dfs1(v,u,deep+1);
        sz[u]+=sz[v];
        if(son[u]==-1 || sz[v]>sz[son[u]])
        {
            son[u]=v;
        }
    }
}
void dfs2(int u,int topf)//第二遍dfs按重儿子走,确定每一条树链
{
    id[u]=++cnt;//确定每个结点对应在树状数组中的结点
    pos[cnt]=u;//由树状数组中的结点对应树中的结点
    top[u]=topf;//确定结点的链顶端结点
    if(son[u]==-1) return;//递归到叶子结点
    dfs2(son[u],topf);//先递归重儿子,保证一条链上的结点在树状数组中的编号是连续的
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(v==fa[u] || v==son[u]) continue;
        dfs2(v,v);//对于每一个轻儿子都有一条从它自己开始的链
    }
}
void update_path(int x,int y,int k)//将路径x-y上的所有结点的值都加k
{
    while(top[x]!=top[y])//不在一条链上,让链顶端结点更深的结点向上爬
    {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);//将x改为链顶端深度更深的那个点
        //对应树状数组的区间为[l,r]
        int l=id[top[x]],r=id[x];
        add(l,k);
        add(r+1,-k);
        x=fa[top[x]];//爬到链顶端结点的父结点
    }
    //直到两个点在一条链上
    if(dep[x]>dep[y]) swap(x,y);//将x改为本身深度更浅的结点
    //结点x和y对应在树状数组中的结点编号
    int l=id[x],r=id[y];
    add(l,k);
    add(r+1,-k);
}
int main()
{
    int N,M,P;
    while(~scanf("%d%d%d",&N,&M,&P))
    {
        cnt=0;
        memset(son,-1,sizeof(son));
        for(int i=0;i<=N;i++) g[i].clear();
        memset(tree,0,sizeof(tree));
        for(int i=1;i<=N;i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=1;i<=M;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        dfs1(1,0,0);
        dfs2(1,1);
        for(int i=1;i<=N;i++)
        {
            add(id[i],a[i]);
            add(id[i]+1,-a[i]);
        }
        for(int i=1;i<=P;i++)
        {
            int x,y,k;
            char op[2];
            scanf("%s",&op);
            if(op[0]=='I')
            {
                scanf("%d%d%d",&x,&y,&k);
                update_path(x,y,k);
            }
            else if(op[0]=='D')
            {
                scanf("%d%d%d",&x,&y,&k);
                update_path(x,y,-k);
            }
            else if(op[0]=='Q')
            {
                scanf("%d",&k);
                printf("%d\n",sum(id[k]));
            }
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37685156/article/details/80402448