树链剖分
树链剖分
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;
}

浙公网安备 33010602011771号