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

重链剖分

时间:2024-08-25 18:53:40浏览次数:11  
标签:剖分 int siz son dep dfn 重链 节点

思想

我先给出一些定义:

定义一个结点的重子节点为其子节点中子树节点数最大的子节点,如有多个,随便取一个作为重儿子,如果没有子节点,就没有重儿子。

定义一个结点的轻子节点为其除重子节点外的子节点。

从这个节点到重子节点的边为重边,到其他子节点的边为轻边

由重边首位相连的链称为重链

把落单的结点也当作重链,那么整棵树就被剖分成若干条重链,可以证明,树上每条路径最多会被分成 \(O(\log{n})\) 条重链。

如图:

预处理

树剖的预处理分为两步。

我们先给出一些定义:

  • \(fa(u)\) 表示节点 \(u\) 的父节点
  • \(dep(u)\) 表示节点 \(u\) 的深度
  • \(siz(u)\) 表示节点 \(u\) 的子树结点个数
  • \(son(u)\) 表示节点 \(u\) 的重儿子
  • \(top(u)\) 表示节点 \(u\) 所在 重链 的顶部节点(深度最小)
  • \(dfn(u)\) 表示节点 \(u\) 的 \(dfs\) 序,也是其在线段树中的编号
  • \(rnk(x)\) 表示 \(dfs\) 序所对应的节点编号,有 \(rnk(dfn(u))=u\)

我们进行两遍 \(dfs\) 预处理这些值,第一遍预处理出 \(fa\),\(son\),\(siz\),\(dep\) ,第二遍预处理出 \(top\),\(dfn\),\(rnk\)。

void dfs1(int u){
    son[u]=-1;
    siz[u]=1;
    for(int i=head[u];i;i=g[i].nxt){
        int v=g[i].to;
        if(!dep[v]){
            dep[v]=dep[u]+1;
            fa[v]=u;
            dfs1(v);
            siz[u]+=siz[v];
            if(son[u]==-1||siz[v]>siz[son[u]]){
                son[u]=v;
            }
        }
    }
    return;
}
int cnt=0;
void dfs2(int u,int t){
    top[u]=t;
    cnt++;
    dfn[u]=cnt;
    rnk[cnt]=u;
    if(son[u]==-1)return;
    dfs2(son[u],t);//先访问重儿子使重链上的点dfn序连续
    for(int i=head[u];i;i=g[i].nxt){
        int v=g[i].to;
        if(v!=son[u]&&v!=fa[u]){
            dfs2(v,v);
        }
    }
    return;
}

LCA

题面

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

思路

这题做法很多,什么倍增,\(RMQ\),\(tarjan\) 之类的。今天我们来讲用重链剖分做。其实很简单,我们只需要让两个节点不断按一条一条重链跳直到两点处于一条重链时取深度低的即可。

代码

#include<iostream>
#include<cstdio>
using namespace std;
struct edge{
    int to;
    int nxt;
}g[1000100];
int head[500100],tot;
void addedge(int x,int y){
    tot++;
    g[tot].to=y;
    g[tot].nxt=head[x];
    head[x]=tot;
    return;
}
int son[500010],top[500010],fa[500010],siz[500010],dep[500010],dfn[500010],rnk[500010];
void dfs1(int u){
    son[u]=-1;
    siz[u]=1;
    for(int i=head[u];i;i=g[i].nxt){
        int v=g[i].to;
        if(!dep[v]){
            dep[v]=dep[u]+1;
            fa[v]=u;
            dfs1(v);
            siz[u]+=siz[v];
            if(son[u]==-1||siz[v]>siz[son[u]]){
                son[u]=v;
            }
        }
    }
    return;
}
int cnt=0;
void dfs2(int u,int t){
    top[u]=t;
    cnt++;
    dfn[u]=cnt;
    rnk[cnt]=u;
    if(son[u]==-1)return;
    dfs2(son[u],t);
    for(int i=head[u];i;i=g[i].nxt){
        int v=g[i].to;
        if(v!=son[u]&&v!=fa[u]){
            dfs2(v,v);
        }
    }
    return;
}
inline int lca(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]>dep[top[v]]){
            u=fa[top[u]];
        }else{
            v=fa[top[v]];
        }
    }
    return dep[u]>dep[v]?v:u;
}
int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int n,m,s;
    cin>>n>>m>>s;
    for(int i=1;i<n;++i){
        int x,y;
        cin>>x>>y;
        addedge(x,y);
        addedge(y,x);
    }
    dep[s]=1;
    dfs1(s);
    dfs2(s,s);
    for(int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        cout<<lca(x,y)<<"\n";
    }
    cout<<flush;
    return 0;
}

【模板】重链剖分/树链剖分

题面

如题,已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值都加上 \(z\)。

  • 2 x y,表示求树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)。

  • 4 x 表示求以 \(x\) 为根节点的子树内所有节点值之和

思路

我们先说路径的修改查询,其实很简单,我们可以像跑 \(LCA\) 一样遍历 \(x\) 到 \(y\) 的路径上的每条重链,由于前面我们预处理时已经保证了一条重链的 \(dfn\) 序连续,我们可以将每个点用其 \(dfn\) 序表示,再维护一个 \(nw\) 数组用来存对应 \(dfn\) 序的初始值,用线段树进行区间修改,区间查询即可。再说子树修改查询,这个更简单,由于子树 \(dfn\) 序连续,直接用线段树修改查询即可。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
struct edge{
    int to;
    int nxt;
}g[1000100];
int head[500100],tot;
int n,m,r,p;
inline void addedge(int x,int y){
    tot++;
    g[tot].to=y;
    g[tot].nxt=head[x];
    head[x]=tot;
    return;
}
ll a[100010];
int son[100010],top[100010],fa[100010],siz[100010],dep[100010],dfn[100010];
ll nw[100010];
void dfs1(int u){
    son[u]=-1;
    siz[u]=1;
    for(int i=head[u];i;i=g[i].nxt){
        int v=g[i].to;
        if(!dep[v]){
            dep[v]=dep[u]+1;
            fa[v]=u;
            dfs1(v);
            siz[u]+=siz[v];
            if(son[u]==-1||siz[v]>siz[son[u]]){
                son[u]=v;
            }
        }
    }
    return;
}
int cnt=0;
void dfs2(int u,int t){
    top[u]=t;
    cnt++;
    dfn[u]=cnt;
    nw[cnt]=a[u];
    if(son[u]==-1)return;
    dfs2(son[u],t);
    for(int i=head[u];i;i=g[i].nxt){
        int v=g[i].to;
        if(v!=son[u]&&v!=fa[u]){
            dfs2(v,v);
        }
    }
    return;
}
struct node{
    ll tag;
    ll val;
    ll l;
    ll r;
}tree[400100];
inline void maketag(int u,ll k){
    tree[u].val+=k*(tree[u].r-tree[u].l+1)%p;
    tree[u].val%=p;
    tree[u].tag+=k;
    return;
}
inline void pushup(int u){
    tree[u].val=(tree[u<<1].val+tree[u<<1|1].val)%p;
    return;
}
inline void pushdown(int u){
    maketag(u<<1,tree[u].tag);
    maketag(u<<1|1,tree[u].tag);
    tree[u].tag=0;
    return;
}
void build(int u,int l,int r){
    tree[u].l=l;
    tree[u].r=r;
    tree[u].tag=0;
    if(l==r){
        tree[u].val=nw[l]%p;
        return;
    }
    int mid=(l+r)>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    pushup(u);
    return;
}
void modify(int u,int l,int r,ll k){
    if(l<=tree[u].l&&tree[u].r<=r){
        maketag(u,k);
        return;
    }
    pushdown(u);
    int mid=(tree[u].l+tree[u].r)>>1;
    if(l<=mid){
        modify(u<<1,l,r,k);
    }
    if(mid<r){
        modify(u<<1|1,l,r,k);
    }
    pushup(u);
    return;
}
ll query(int u,int l,int r){
    if(l<=tree[u].l&&tree[u].r<=r){
        return tree[u].val;
    }
    pushdown(u);
    ll ans=0;
    int mid=(tree[u].l+tree[u].r)>>1;
    if(l<=mid){
        ans+=query(u<<1,l,r);
    }
    if(mid<r){
        ans+=query(u<<1|1,l,r);
    }
    return ans%p;
}
void mdfpath(int u,int v,ll k){
    k%=p;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        modify(1,dfn[top[u]],dfn[u],k);
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    modify(1,dfn[u],dfn[v],k);
    return;
}
ll qrypath(int u,int v){
    ll ans=0;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        ans+=query(1,dfn[top[u]],dfn[u]);
        ans%=p;
        u=fa[top[u]];
    }
    if(dep[u]>dep[v])swap(u,v);
    ans+=query(1,dfn[u],dfn[v]);
    ans%=p;
    return ans;
}
void mdftree(int u,int k){
    modify(1,dfn[u],dfn[u]+siz[u]-1,k%p);
    return;
}
ll qrytree(int u){
    return query(1,dfn[u],dfn[u]+siz[u]-1);
}
int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    cin>>n>>m>>r>>p;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<n;i++){
        int x,y;
        cin>>x>>y;
        addedge(x,y);
        addedge(y,x);
    }
    dep[r]=1;
    dfs1(r);
    dfs2(r,r);
    build(1,1,n);
    while(m--){
        int op,x,y,z;
        cin>>op>>x;
        if(op==1){
            cin>>y>>z;
            mdfpath(x,y,z%p);
        }
        if(op==2){
            cin>>y;
            cout<<qrypath(x,y)<<"\n";
        }
        if(op==3){
            cin>>z;
            mdftree(x,z%p);
        }
        if(op==4){
            cout<<qrytree(x)<<"\n";
        }
    }
    cout<<flush;
    return 0;
}

标签:剖分,int,siz,son,dep,dfn,重链,节点
From: https://www.cnblogs.com/pengdave/p/18379322

相关文章

  • 算法总结-树链剖分
    算法总结-树链剖分概念重儿子(红点):父节点\(f\)的子节点中子树大小最大的儿子。轻儿子(蓝点):父节点\(f\)的子节点中除重儿子以外的节点。重链(橙圈框出的链):链顶为轻儿子,由重儿子组成的链。重链剖分用处:将树上任意一条路径划分成不超过\(\log{n}\)条连续的链(如实......
  • 对树链剖分的爱 题解
    前言题目链接:洛谷。题意简述给出一棵\(n\)个节点以\(1\)为根的有根树。对于第\(2\leqi\leqn\)个节点,其父亲\(f_i\)在\([l_i,r_i]\)中均匀随机。每个树的边有边权,初始为\(0\)。现在有\(m\)次操作,第\(i\)次操作表示将\((u_i,v_i)\)的路径上所有的边的权值统......
  • 算法学习笔记之树链剖分
    算法学习笔记之(熟练跑分)树链剖分PART1首先是第一部份,也就是熟练跑分最最最基础的用法——求\(LCA\)首先是树链剖分//图片出自董晓算法大概就是这样本质就是根据子树大小将一颗树剖分成若干条链然后更加方便地处理/加速处理信息所以直接上代码?不,还要证明树链剖......
  • 树链剖分
    具体见OI-wiki,下面是一些补充重链要求是极大的每个点都在某一个重链中,如果一个点是重子节点,那么其在与其父亲所连的边的重链中,否则在与其重子节点所连的边的重链中这一段的原因:我们走重链是不用关心的,因为同一重链的dfs序是连续的,我们可以用其他数据结构维护,我们只用关心这条......
  • 树链剖分
    前置知识时间戳在树的\(DFS\)中,以每个节点第一次被访问的顺序,依次给予\(N\)个点\(1-n\)的标记,通常用\(dfn[x]\)表示\(x\)号节点的时间戳。\(DFS\)序进行\(DFS\)时,对每个节点进入递归后以及回溯前各记录一次该点编号,最后产生\(2-n\)的序列即是\(DFS\)序,可设\(L[X]\)和\(R[X]\)......
  • Open3D 三维重建-Delaunay Triangulation (德劳内三角剖分)
    目录一、概述1.1原理1.2实现步骤1.3应用二、代码实现2.1关键函数2.2完整代码三、实现效果3.1原始点云3.2重建后点云Open3D点云算法汇总及实战案例汇总的目录地址:Open3D点云算法与点云深度学习案例汇总(长期更新)-CSDN博客一、概述        德劳内三角剖......
  • 树链剖分
    定义把树剖成一条条不相交的链,对树的操作就转化成了对链的操作概念重儿子:对于每一个非叶子节点,它的儿子中以那个儿子为根的子树节点数最大的儿子为该节点的重儿子轻儿子:对于每一个非叶子节点,它的儿子中非重儿子的剩下所有儿子即为轻儿子重边:连接任意两个重儿子的边叫做重......
  • MATLAB生成各类区域网格剖分
    一、双洞模型代码:hg=[1111111120-20010-10-20210-10020-2012120-2012101111000000001111000000000000111122221111];ug=[1111010-1......
  • MATLAB: 使用Delaunay三角剖分构建点云网格
    在计算机图形学和计算几何学中,Delaunay三角剖分a是一种常用的方法,用于将点云数据转换为三角形网格,MATLAB提供了内置函数来执行Delaunay三角剖分,并生成适用于点云可视化和分析的三角网格,本文将介绍如何使用MATLAB进行点云的Delaunay三角剖分,并提供相应的源代码。步骤一:导入点云......
  • 树链剖分
    P3379【模板】最近公共祖先(LCA)dfs1:处理一个点的深度、父结点、子树大小,重儿子。dfs2:记录每个点的最顶部。query:哪个top的深度小跳哪个。#include<bits/stdc++.h>usingnamespacestd;constintN=500010,M=1000010;structedge{intto,next;}e[......