【10.10】重链剖分

重链剖分

参考blog:https://www.cnblogs.com/ivanovcraft/p/9019090.html

顾名思义,重链剖分就是将一颗有根树剖分成若干条重链,然后用数据结构维护这些重链的信息。

典型问题

对于一颗有根树有如下操作:

  1. 将树从x到y结点最短路径上所有节点的值都加上z
  2. 求树从x到y结点最短路径上所有节点的值之和

明确概念

重儿子:父亲节点的所有儿子中子树大小(size)最大的儿子;
轻儿子:父亲节点中除了重儿子以外的儿子;
重边:父亲结点和重儿子连成的边;
轻边:父亲节点和轻儿子连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;

预处理变量

\(size[x]\) 子树大小
\(dep[x]\) 节点深度
\(fa[x]\) 节点父亲
\(son[x]\) 节点的重儿子(叶子节点没有重儿子)
\(top[x]\) 节点所在重链顶端
\(id[x]\) 对节点的重新编号(dfs序)

实现

预处理由两遍dfs实现

dfs1 处理出size,dep,fa,son树组:

void dfs1(int u,int f){
    size[u]=1; dep[u]=dep[f]+1; fa[u]=f;
    int mx=0;
    for (int v:e[u]){
        if (v==f) continue;
        dfs1(v,u);
        if (size[v]>mx){mx=size[v]; son[u]=v;}
        size[u]+=size[v];
    }
    return;
}

dfs2 处理出top,id树组:

为了方便维护数据结构,我们要保证一条重链上的dfs序连续。

int vis[MAXN],cnt;
void dfs2(int u,int t){
    vis[u]=1;
    top[u]=t; id[u]=++cnt;
    add(1,1,n,cnt,cnt,a[u]); //先进入重儿子,保证重链上的dfs序连续
    if (son[u]) dfs2(son[u],t);
    for (int v:e[u]){
        if (vis[v]||son[u]==v) continue;
        dfs2(v,v);
    }
    return;
}

查询

其实就是求LCA,把沿途的所有链的信息加和。重链剖分就是用跳top加速LCA的过程。由于使用数据结构维护重链信息,加和得以快速实现。

修改

还是跳top加速,沿途重链上的数据结构区间修改。


以下题目思路不难想,主要是细节多

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

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

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

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

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

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

\(N,M \leq 1e5\)

Code

#include <bits/stdc++.h>
#define MAXN 100003
using namespace std;
int n,m,R,P;
int a[MAXN];
/*----*/
long long tree[MAXN<<2],tag[MAXN<<2];
void pushup(int x){
    tree[x]=tree[x<<1]+tree[x<<1|1];
}
void pushdown(int x,int l,int r,int mid){
    if(tag[x]==0) return;
    tag[x<<1]+=tag[x]; tag[x<<1|1]+=tag[x];
    tree[x<<1]+=tag[x]*(mid-l+1); tree[x<<1|1]+=tag[x]*(r-mid);
    tag[x]=0;
}
void add(int tr,int l,int r,int x,int y,int z){
    if (x<=l&&r<=y){
        tree[tr]+=z*(r-l+1);
        tag[tr]+=z;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(tr,l,r,mid);
    if (mid>=x) add(tr<<1,l,mid,x,y,z);
    if (mid<y) add(tr<<1|1,mid+1,r,x,y,z);
    pushup(tr);
    return;
}
long long query(int tr,int l,int r,int x,int y){
    if (x<=l&&r<=y){
        return tree[tr];
    }
    int mid=(l+r)>>1;
    pushdown(tr,l,r,mid);
    long long res=0;
    if (mid>=x) res+=query(tr<<1,l,mid,x,y);
    if (mid<y) res+=query(tr<<1|1,mid+1,r,x,y);
    return res;
}
/*--线段树--*/
vector <int> e[MAXN];
int size[MAXN],dep[MAXN],fa[MAXN],son[MAXN],top[MAXN],id[MAXN];
void dfs1(int u,int f){
    size[u]=1; dep[u]=dep[f]+1; fa[u]=f;
    int mx=0;
    for (int v:e[u]){
        if (v==f) continue;
        dfs1(v,u);
        if (size[v]>mx){mx=size[v]; son[u]=v;}
        size[u]+=size[v];
    }
    return;
}
int vis[MAXN],cnt;
void dfs2(int u,int t){
    vis[u]=1;
    top[u]=t; id[u]=++cnt;
    add(1,1,n,cnt,cnt,a[u]);
    if (son[u]) dfs2(son[u],t);
    for (int v:e[u]){
        if (vis[v]||son[u]==v) continue;
        dfs2(v,v);
    }
    return;
}
void ADD1(int u,int v,int z){
    while (top[u]!=top[v]){
        if (dep[top[u]]<dep[top[v]]) swap(u,v);
        add(1,1,n,id[top[u]],id[u],z);
        u=fa[top[u]];
    }
    if (dep[u]<dep[v]) swap(u,v);
    add(1,1,n,id[v],id[u],z);
    return;
}
long long SUM1(int u,int v){
    long long res=0;
    while (top[u]!=top[v]){
        if (dep[top[u]]<dep[top[v]]) swap(u,v);
        res+=query(1,1,n,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if (dep[u]<dep[v]) swap(u,v);
    res+=query(1,1,n,id[v],id[u]);
    return res;
}
void ADD2(int u,int z){
    add(1,1,n,id[u],id[u]+size[u]-1,z);
    return;
}
long long SUM2(int u){
    return query(1,1,n,id[u],id[u]+size[u]-1);
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m>>R>>P;
    for (int i=1;i<=n;i++) cin>>a[i];
    for (int i=1;i<=n-1;i++){
        int u,v; cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(R,0);
    dfs2(R,R);
    while (m--){
        int type; cin>>type;
        if (type==1){
            int x,y,z; cin>>x>>y>>z;
            ADD1(x,y,z);
        } else if (type==2){
            int x,y; cin>>x>>y;
            cout<<SUM1(x,y)%P<<endl;
        } else if (type==3){
            int x,z; cin>>x>>z;
            ADD2(x,z);
        } else {
            int x; cin>>x;
            cout<<SUM2(x)%P<<endl;
        }
    }
    return 0;
}

P2146 [NOI2015] 软件包管理器

题目要求支持两种操作

1,install x:表示安装软件包x

2,uninstall x:表示卸载软件包x

对于操作一,我们可以统计x到根节点未安装的软件包的个数,然后区间修改为已安装

对于操作二,我们可以统计x所在子树已安装软件包的个数,然后将子树修改为未安装

询问每次操作后软件包安装状态改变的个数。

\(n,q \leq 1e5\)

Code

#include <bits/stdc++.h>
#define MAXN 100003
using namespace std;
int n,q;
vector <int> e[MAXN];
int tree[MAXN<<2];
struct TAG{
    int val;
    int time; //懒标记时间戳
}tag[MAXN<<2];
int TIME;
void pushup(int x){
    tree[x]=tree[x<<1]+tree[x<<1|1];
}
void pushdown(int x,int l,int r,int mid){
    if (tag[x].time==0) return;
    if (tag[x<<1].time<tag[x].time) tag[x<<1]=tag[x];
    if (tag[x<<1|1].time<tag[x].time) tag[x<<1|1]=tag[x];
    tree[x<<1]=tag[x<<1].val*(mid-l+1); tree[x<<1|1]=tag[x<<1|1].val*(r-mid);
    tag[x].val=0,tag[x].time=0;
}
void modify(int tr,int l,int r,int x,int y,int z){
    if (x<=l&&r<=y){
        tree[tr]=z*(r-l+1);
        tag[tr].val=z;
        tag[tr].time=++TIME;
        return;
    }
    int mid=(l+r)>>1;
    if (mid>=x) modify(tr<<1,l,mid,x,y,z);
    if (mid<y) modify(tr<<1|1,mid+1,r,x,y,z);
    pushup(tr);
    return;
}
int query(int tr,int l,int r,int x,int y){
    if (x<=l&&r<=y){
        return tree[tr];
    }
    int mid=(l+r)>>1,res=0;
    pushdown(tr,l,r,mid);
    if (mid>=x) res+=query(tr<<1,l,mid,x,y);
    if (mid<y) res+=query(tr<<1|1,mid+1,r,x,y);
    return res;
}
int size[MAXN],dep[MAXN],fa[MAXN],son[MAXN],top[MAXN],id[MAXN];
void dfs1(int u,int f){
    size[u]=1; dep[u]=dep[f]+1; fa[u]=f;
    int mx=0;
    for (int v:e[u]){
        if (v==f) continue;
        dfs1(v,u);
        size[u]+=size[v];
        if (size[v]>mx) {mx=size[v]; son[u]=v;}
    }
    return;
}
int cnt;
void dfs2(int u,int t){
    top[u]=t; id[u]=++cnt;
    if (son[u]) dfs2(son[u],t);
    for (int v:e[u]){
        if (son[u]==v||fa[u]==v) continue;
        dfs2(v,v);
    }
    return;
}
void CHG1(int u,int v,int z){
    while (top[u]!=top[v]){
        if (dep[top[u]]<dep[top[v]]) swap(u,v);
        modify(1,1,n,id[top[u]],id[u],z);
        u=fa[top[u]];
    }
    if (dep[u]<dep[v]) swap(u,v);
    modify(1,1,n,id[v],id[u],z);
    return;
}
int SUM1(int u,int v){
    int res=0;
    while (top[u]!=top[v]){
        if (dep[top[u]]<dep[top[v]]) swap(u,v);
        res+=query(1,1,n,id[top[u]],id[u]);
        u=fa[top[u]];
    }
    if (dep[u]<dep[v]) swap(u,v);
    res+=query(1,1,n,id[v],id[u]);
    return res;
}
void CHG2(int u,int z){
    modify(1,1,n,id[u],id[u]+size[u]-1,z);
    return;
}
int SUM2(int u){
    return query(1,1,n,id[u],id[u]+size[u]-1);
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for (int u=1;u<=n-1;u++){
        int v; cin>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(0,MAXN);
    dfs2(0,0);
    cin>>q;
    while (q--){
        string type; cin>>type;
        if (type=="install"){
            int x; cin>>x;
            int res=-SUM1(0,x); //减去原本有的
            CHG1(0,x,1);
            res+=SUM1(0,x); //加上上面所有的
            cout<<res<<endl; //等于新加的
        } else {
            int x; cin>>x;
            cout<<SUM2(x)<<endl; //原本有的就是要被删的
            CHG2(x,0); //删除
        }
    }
    return 0;
}

P2486 [SDOI2011] 染色

给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:

  1. 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\)\(b\))都染成颜色 \(c\)
  2. 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量。

\(n,m \leq 1e5\)

Code

#include <bits/stdc++.h>
#define MAXN 100003
using namespace std;
int n,m;
int a[MAXN],b[MAXN];
vector <int> e[MAXN];
struct TREE{
    int cl; int cr;
    int k;
    TREE operator+(const TREE &B)const{ //重载加法,方便合并
        if (B.k==0) return (TREE){cl,cr,k};
        if (k==0) return B;
        TREE res;
        res.cl=cl; res.cr=B.cr;
        if (cr!=B.cl) res.k=k+B.k;
        else res.k=k+B.k-1;
        return res;
    }
}tree[MAXN<<2];
struct TAG{
    int val;
    int time;
}tag[MAXN<<2];
int TIME;
void pushup(int x){
    tree[x]=tree[x<<1]+tree[x<<1|1];
}
void pushdown(int x,int l,int r,int mid){
    if (tag[x].time==0) return;
    if (tag[x<<1].time<tag[x].time) tag[x<<1]=tag[x];
    if (tag[x<<1|1].time<tag[x].time) tag[x<<1|1]=tag[x];
    tree[x<<1].cl=tree[x<<1].cr=tag[x<<1].val; tree[x<<1|1].cl=tree[x<<1|1].cr=tag[x<<1|1].val;
    tree[x<<1].k=1; tree[x<<1|1].k=1; //懒标记覆盖
    tag[x].val=0,tag[x].time=0;
}
void build(int tr,int l,int r){
    if (l==r) {tree[tr].cl=tree[tr].cr=b[l]; tree[tr].k=1; return;}
    int mid=(l+r)>>1;
    build(tr<<1,l,mid);
    build(tr<<1|1,mid+1,r);
    pushup(tr);
    return;
}
void modify(int tr,int l,int r,int x,int y,int c){
    if (x<=l&&r<=y){
        tree[tr].cl=tree[tr].cr=c; tree[tr].k=1;
        tag[tr].val=c;
        tag[tr].time=++TIME;
        return;
    }
    int mid=(l+r)>>1;
    pushdown(tr,l,r,mid);
    if (mid>=x) modify(tr<<1,l,mid,x,y,c);
    if (mid<y) modify(tr<<1|1,mid+1,r,x,y,c);
    pushup(tr);
    return;
}
TREE query(int tr,int l,int r,int x,int y){
    if (x<=l&&r<=y){
        return tree[tr];
    }
    int mid=(l+r)>>1;
    TREE res={0,0,0};
    pushdown(tr,l,r,mid);
    if (mid>=x) res=res+query(tr<<1,l,mid,x,y);
    if (mid<y) res=res+query(tr<<1|1,mid+1,r,x,y);
    return res;
}
int size[MAXN],dep[MAXN],fa[MAXN],son[MAXN],top[MAXN],id[MAXN];
void dfs1(int u,int f){
    size[u]=1; dep[u]=dep[f]+1; fa[u]=f;
    int mx=0;
    for (int v:e[u]){
        if (v==f) continue;
        dfs1(v,u);
        size[u]+=size[v];
        if (size[v]>mx) {mx=size[v]; son[u]=v;}
    }
    return;
}
int cnt;
void dfs2(int u,int t){
    top[u]=t; id[u]=++cnt;
    if (son[u]) dfs2(son[u],t);
    for (int v:e[u]){
        if (son[u]==v||fa[u]==v) continue;
        dfs2(v,v);
    }
    return;
}
void CHG1(int u,int v,int c){
    while (top[u]!=top[v]){
        if (dep[top[u]]<dep[top[v]]) swap(u,v);
        modify(1,1,n,id[top[u]],id[u],c);
        u=fa[top[u]];
    }
    if (dep[u]<dep[v]) swap(u,v);
    modify(1,1,n,id[v],id[u],c);
    return;
}
TREE q[2][MAXN]; int CNT[2];
TREE SUM1(int u,int v){
    bool side=0; CNT[0]=CNT[1]=0;
    while (top[u]!=top[v]){
        if (dep[top[u]]<dep[top[v]]) {swap(u,v); side=!side;}
        TREE RT=query(1,1,n,id[top[u]],id[u]); if (!side) swap(RT.cl,RT.cr); //注意合并时块的方向
        q[side][++CNT[side]]=RT;
        u=fa[top[u]];
    }
    if (dep[u]<dep[v]) {swap(u,v); side=!side;}
    TREE RT=query(1,1,n,id[v],id[u]); if (!side) swap(RT.cl,RT.cr);
    TREE res={0,0,0};
    for (int i=1;i<=CNT[0];i++) res=res+q[0][i];
    res=res+RT;
    for (int i=CNT[1];i>=1;i--) res=res+q[1][i]; //从一边到另一边顺序合并
    return res;
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for (int i=1;i<=n;i++) cin>>a[i];
    for (int i=1;i<=n-1;i++){
        int u,v; cin>>u>>v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1,0);
    dfs2(1,1);
    for (int i=1;i<=n;i++) b[id[i]]=a[i];
    build(1,1,n);
    while (m--){
        char type; cin>>type;
        if (type=='C'){
            int u,v,c; cin>>u>>v>>c;
            CHG1(u,v,c);
        } else {
            int u,v; cin>>u>>v;
            cout<<SUM1(u,v).k<<endl;
        }
    }
    return 0;
}

over.

posted @   EcapsXD  阅读(6)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示