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

D12【模板】重链剖分/树链剖分_哔哩哔哩_bilibili

 

P3384 【模板】重链剖分/树链剖分 - 洛谷

给一颗树,4种操作:路径加,路径和,子树加,子树和。

 

树链剖分 - OI Wiki

重儿子 表示其子结点中子树最大的子结点.

轻儿子 表示剩余的所有子结点.

连向重儿子的边为 重边

若干条首尾衔接的重边构成 重链.每条重链的头一定是 子结点.那么整棵树就被剖分成若干条重链.

树上每个结点都属于且仅属于一条重链

1. 第一个 DFS 记录每个结点的父结点(𝑓𝑎)、深度(𝑑𝑒𝑝)、子树大小(𝑠𝑖𝑧)、重儿子(𝑠𝑜𝑛).

2. 第二个 DFS 记录所在链的链顶(𝑡𝑜𝑝)、重边优先遍历时的 DFS 序(dfn)、DFS 序对应的结点编号(𝑟𝑛𝑘).

  • dfn⁡(𝑥) 表示结点 𝑥 的 DFS 序,也是其在线段树中的编号.
  • rnk⁡(𝑥) 表示 DFS 序所对应的结点编号,有 rnk⁡(dfn⁡(𝑥)) =𝑥

3. 把树拆成链,用链建立线段树.

4. 路径拆成链,线段树上做区修、区查.

 

// 树链剖分 O(mlognlogn)
#include<bits/stdc++.h>
using namespace std;

#define int long long
const int N=100010;
int n,m,R,P,w[N];
vector<int> e[N];

// 树链剖分
int fa[N],dep[N],siz[N],son[N];
int top[N],dfn[N],rnk[N];

void dfs1(int u,int f){ //搞fa,dep,siz,son
  fa[u]=f,dep[u]=dep[f]+1,siz[u]=1;
  for(int v:e[u])if(v!=f){
    dfs1(v,u);
    siz[u]+=siz[v];
    if(siz[son[u]]<siz[v]) son[u]=v; 
  }
}
void dfs2(int u,int t){ //搞top,dfn,rnk
  top[u]=t,dfn[u]=++dfn[0],rnk[dfn[0]]=u;
  if(son[u]) dfs2(son[u],t);
  for(int v:e[u])if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}

struct SGT{ //线段树
  #define lc (u<<1)
  #define rc (u<<1|1)
  int sum[N<<2],add[N<<2]; //节点和,节点加
  
  void pushup(int u){
    sum[u]=sum[lc]+sum[rc];
  }
  void pushdown(int u,int l,int r,int mid){
    if(add[u]){
      sum[lc]+=add[u]*(mid-l+1);
      sum[rc]+=add[u]*(r-mid);
      add[lc]+=add[u];
      add[rc]+=add[u];
      add[u]=0;
    }
  }
  void build(int u=1,int l=1,int r=n){ //建线段树
    sum[u]=w[rnk[l]]; 
    if(l==r) return;
    int mid=l+r>>1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    pushup(u);
  }
  void upd(int x,int y,int k,int u=1,int l=1,int r=n){ //线段树修改
    if(x>r||y<l) return;
    if(x<=l&&r<=y){
      sum[u]+=k*(r-l+1);
      add[u]+=k;
      return;
    }
    int mid=l+r>>1;
    pushdown(u,l,r,mid);
    upd(x,y,k,lc,l,mid);
    upd(x,y,k,rc,mid+1,r);
    pushup(u);
  }
  void upd_path(int u,int v,int k){ //修改路径
    while(top[u]!=top[v]){
      if(dep[top[u]]<dep[top[v]]) swap(u,v);
      upd(dfn[top[u]],dfn[u],k);
      u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    upd(dfn[v],dfn[u],k); //最后一段
  }
  void upd_tree(int u,int k){ //修改子树
    upd(dfn[u],dfn[u]+siz[u]-1,k);
  }
  
  int ask(int x,int y,int u=1,int l=1,int r=n){ //线段树查询
    if(x>r||y<l) return 0;
    if(x<=l&&r<=y) return sum[u];
    int mid=l+r>>1;
    pushdown(u,l,r,mid);
    return ask(x,y,lc,l,mid)+ask(x,y,rc,mid+1,r);
  }
  int ask_path(int u,int v){ //查询路径
    int res=0;
    while(top[u]!=top[v]){
      if(dep[top[u]]<dep[top[v]]) swap(u,v);
      res+=ask(dfn[top[u]],dfn[u]);
      u=fa[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    res+=ask(dfn[v],dfn[u]); //最后一段
    return res;
  }
  int ask_tree(int u){ //查询子树
    return ask(dfn[u],dfn[u]+siz[u]-1);
  }
}S;

signed 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>>w[i];
  for(int i=0,a,b; i<n-1; i++){
    cin>>a>>b;
    e[a].push_back(b); e[b].push_back(a);
  }
  
  dfs1(R,0); 
  dfs2(R,R); //树剖
  S.build(); //建线段树
  for(int t,a,b,c;m--;){
    cin>>t>>a;
    if(t==1) cin>>b>>c,S.upd_path(a,b,c); //路径加
    else if(t==3) cin>>c,S.upd_tree(a,c); //子树加
    else if(t==2) cin>>b,cout<<S.ask_path(a,b)%P<<"\n"; //路径和
    else cout<<S.ask_tree(a)%P<<"\n"; //子树和
  }
}

 

Luogu P2486 [SDOI2011] 染色

Luogu P4211 [LNOI2014] LCA

Luogu P3313 [SDOI2014] 旅行

Luogu P4219 [BJOI2014] 大融合

Luogu P4216 [SCOI2015] 情报传递

Luogu P2146 [NOI2015] 软件包管理器

Luogu P5305 [GXOI/GZOI2019] 旧词

Luogu P1505 [国家集训队] 旅游

 

posted @ 2022-05-28 13:26  董晓  阅读(2404)  评论(2)    收藏  举报