【10.10】重链剖分
重链剖分
参考blog:https://www.cnblogs.com/ivanovcraft/p/9019090.html
顾名思义,重链剖分就是将一颗有根树剖分成若干条重链,然后用数据结构维护这些重链的信息。
典型问题
对于一颗有根树有如下操作:
- 将树从x到y结点最短路径上所有节点的值都加上z
- 求树从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\) 个操作,操作分为两种:
- 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\) 和 \(b\))都染成颜色 \(c\)。
- 询问节点 \(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.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 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)