树链剖分

树链剖分


luogu3384

首先是洛谷的树链剖分的模板题,题目描述,已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

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

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

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

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

#include<bits/stdc++.h>
#define ll long long
#define nl '\n'
using namespace std;

vector<int>a;//节点的权值
vector<vector<int>>node;//树的存储
vector<int>depth,heavy,sz,parent;//深度、重节点、子树大小、父节点
vector<int>pos,head,val;//新的序列顺序、每条链的链头、新序列的权值
int tot=0;//节点数目

void dfs1(int x,int p){
    if(p!=-1)depth[x]=depth[p]+1;
    parent[x]=p;
    int siz=0;
    sz[x]=1,heavy[x]=x;
    for(auto v:node[x]){
        if(v!=p){
            dfs1(v,x);
            sz[x]+=sz[v];
            if(sz[v]>siz)siz=sz[v],heavy[x]=v;
        }
    }
}

void dfs2(int x,int top){
    head[x]=top;
    pos[x]=++tot;
    if(heavy[x]!=x)dfs2(heavy[x],top);
    for(auto v:node[x]){
        if(v!=parent[x]&&v!=heavy[x]){
            dfs2(v,v);
        }
    }
}

//基本的线段树
class segmentree{
public:
    vector<int>seg,tag;
    int mod;
    segmentree (int _n,int p){
        seg.resize(_n<<2);
        tag.resize(_n<<2);
        mod=p;
    }
    void build(int l,int r,int x){
        if(l==r){
            seg[x]=val[l]%mod;
            return;
        }
        int mid=l+r>>1;
        build(l,mid,x<<1);
        build(mid+1,r,x<<1|1);
        seg[x]=(seg[x<<1]+seg[x<<1|1])%mod;
    }
    void down(int l,int r,int p){
        int mid=l+r>>1;
        if(tag[p]==0)return;
        seg[p<<1]+=((mid-l+1)*tag[p])%mod;
        seg[p<<1|1]+=((r-mid)*tag[p])%mod;
        tag[p<<1]+=tag[p]%mod;
        tag[p<<1|1]+=tag[p]%mod;
        tag[p]=0;
    }

    void update(int ml,int mr,int l,int r,int p,int w){
        if(ml<=l&&mr>=r){
            tag[p]+=w;
            seg[p]+=((r-l+1)*w)%mod;
            return;
        }
        int mid=l+r>>1;
        down(l,r,p);
        if(ml<=mid)update(ml,mr,l,mid,p<<1,w);
        if(mr>mid)update(ml,mr,mid+1,r,p<<1|1,w);
        seg[p]=(seg[p<<1]+seg[p<<1|1])%mod;
    }

    int query(int ml,int mr,int l,int r,int p){
        int res=0;
        if(ml<=l&&mr>=r)return seg[p];
        int mid=l+r>>1;
        down(l,r,p);
        if(ml<=mid)res+=query(ml,mr,l,mid,p<<1)%mod;
        if(mr>mid)res+=query(ml,mr,mid+1,r,p<<1|1)%mod;
        return res%mod;
    }

};


signed main() {
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);  

    int n,m,r,p;
    cin>>n>>m>>r>>p;r--;
  
  //节点权值
    a.resize(n);
    for(int i=0;i<n;i++)cin>>a[i];
  
  //树的基本信息
    node.resize(n);
    depth.resize(n),heavy.resize(n),sz.resize(n),parent.resize(n);
    for(int i=1;i<n;i++){
        int x,y;
        cin>>x>>y;
        x--,y--;//节点均从0开始
        node[x].push_back(y);
        node[y].push_back(x);
    }
  
    head.resize(n),pos.resize(n);
  //得到树的基本信息
    dfs1(r,-1);
  //树链剖分
    dfs2(r,r);

  //新序列对应的权值
    val.resize(n+1);
    for(int i=0;i<n;i++)val[pos[i]]=a[i];
    
  //建树
    segmentree tree(n,p);
    tree.build(1,n,1);
  
  //子树查询
    auto query=[&](int x,int y){
        int res=0;
        while(head[x]!=head[y]){
           if(depth[head[x]]<depth[head[y]])swap(x,y);
           res+=tree.query(pos[head[y]],pos[y],1,n,1)%p;
           y=parent[head[y]];
        }
        if(depth[x]>depth[y])swap(x,y);
        res+=tree.query(pos[x],pos[y],1,n,1)%p;
        return res%p;
    };

  //子树更新
    auto update=[&](int x,int y,int w){
        while(head[x]!=head[y]){
            if(depth[head[x]]>depth[head[y]])swap(x,y);
            tree.update(pos[head[y]],pos[y],1,n,1,w);
            y=parent[head[y]];
        }
        if(depth[x]>depth[y])swap(x,y);
        tree.update(pos[x],pos[y],1,n,1,w);
    };
    
//操作遍历
    while(m--){
        int op,x;
        cin>>op>>x;x--;
        if(op==1){
          //路径更新
            int y,z;
            cin>>y>>z;y--;
            update(x,y,z);
        }else if(op==2){
          //路径查询(类似求LCA)
            int y;
            cin>>y;y--;
            cout<<query(x,y)%p<<nl;
        }else if(op==3){
          //子树更新(起点为pos[x],终点为pos[x]+sz[x]-1)
            int y;
            cin>>y;
            tree.update(pos[x],pos[x]+sz[x]-1,1,n,1,y);
        }else{
          //子树查询
            cout<<tree.query(pos[x],pos[x]+sz[x]-1,1,n,1)%p<<nl;
        }
    }
    return 0;
}

leetcode3553

下面是LeetCode 3553的实现,特别的这道题给定的边权,上面的模板题给定的是每个点的权值,而对于给定边的权值,可以将其转换为子节点的权值,由于遍历一棵树总会存在 parent 和 son,所以总是把边权作为该边对应son节点的点权值,需要改变的地方,首先要在遍历时,更新val的值,其次,在查询两点距离时,不要把额外的边权给加上了。

问题要求 给定三个点,求三个点覆盖的最小边权之和,值得注意的是,权值之和的求法是:从 \(x\rightarrow y\) ,再 \(y \rightarrow z\) ,最后 \(z\rightarrow x\) ,形成一个环,最终结果就是 \((xy+yz+zx)/2\) ,这样省去了对多种情况的讨论。

vector<vector<pair<int,int>>>node;
vector<int>depth,heavy,sz,parent;
vector<int>pos,head,val;
int tot=0;

void dfs1(int x,int p){
    parent[x]=p;
    depth[x]=(p==-1?0:depth[p]+1);
    sz[x]=1; heavy[x]=-1;
    for(auto [v,w]:node[x]){
        if(v==p) continue;
        dfs1(v,x);
        sz[x]+=sz[v];
        if(heavy[x]==-1||sz[v]>sz[heavy[x]]) heavy[x]=v;
    }
}

void dfs2(int x,int top){
    head[x]=top;
    pos[x]=++tot;
    for(auto [v, w]:node[x]){
        if(v==parent[x]) val[pos[x]] = w; // 把边权赋给子节点的dfs序位置
    }
    if(heavy[x]!=-1) dfs2(heavy[x],top);
    for(auto [v,w]:node[x]){
        if(v!=parent[x]&&v!=heavy[x]) dfs2(v,v);
    }
}

class segmentree{
public:
    vector<int>seg,val;
    segmentree (int _n,vector<int>&_val){
        seg.resize(_n<<2);
        val=_val;
    }
    void build(int l,int r,int x){
        if(l==r){
            seg[x]=val[l]; return;
        }
        int mid=(l+r)>>1;
        build(l,mid,x<<1);
        build(mid+1,r,x<<1|1);
        seg[x]=seg[x<<1]+seg[x<<1|1];
    }
    int query(int l,int r,int x,int ql,int qr){
        if(ql<=l&&r<=qr)return seg[x];
        int mid=(l+r)>>1,res=0;
        if(ql<=mid)res=res+query(l,mid,x<<1,ql,qr);
        if(qr>mid)res=res+query(mid+1,r,x<<1|1,ql,qr);
        return res;
    }
};

vector<int> minimumWeight(vector<vector<int>>& edges, vector<vector<int>>& queries) {
    int n=edges.size()+1;
    node.resize(n);
    depth.resize(n);
    heavy.resize(n);
    sz.resize(n);
    parent.resize(n);
    pos.resize(n);
    head.resize(n);
    val.resize(n+1);
    for(auto e:edges){
        node[e[0]].emplace_back(e[1],e[2]);
        node[e[1]].emplace_back(e[0],e[2]);
    }
    dfs1(0,-1);
    dfs2(0,0);
    segmentree T(n,val);
    T.build(1,n,1);

    auto query=[&](int x,int y){
        int res=0;
        while(head[x]!=head[y]){
          if(depth[head[x]]<depth[head[y]])swap(x,y);
          res+=T.query(1,n,1,pos[head[x]],pos[x]); // 这里与点权保持一致,在过渡到head时,再跳就跳到上一级了,所以这个位置的权值需要加,否则会缺失。
          x=parent[head[x]];
      	}
        if(depth[x]>depth[y])swap(x,y);
        res+=T.query(1,n,1,pos[x]+1,pos[y]);// 这里与之前点权不同,这里如果是pos[x],就把此时的LCA的上面那条边也加上了,所以是不可以的
        return res;
    };

    vector<int>res;
    for(auto q:queries){
        int x=q[0],y=q[1],z=q[2];
        int xy=query(x,y),yz=query(y,z),zx=query(z,x);   
        res.emplace_back((xy+yz+zx)/2);
    }
    return res;
}

其他

LCA的实现

int lca(int x, int y) {
    while (head[x] != head[y]) {
        if (depth[head[x]] < depth[head[y]]) swap(x, y);
        x = parent[head[x]];
    }
    return depth[x] < depth[y] ? x : y;
}
posted @ 2025-03-12 20:46  亦可九天揽月  阅读(22)  评论(0)    收藏  举报