树剖笔记
之前学的,但是这种恶心的东西一天不打就忘了。
为了之后会议方便,就简单的写一下吧。
树剖,顾名思义就是把树剖成一条一条的链。但是要按照儿子个数分成轻链和重链。
每一条链上节点的序号是连续的。可以用线段树或其他数据结构维护信息。
dfs
可以用两个 dfs 解决。
dfs1干的事:
- 标记每个点的深度 \(dep\)
- 标记每个点的父亲 \(fa\)
- 标记每个非叶子节点的子树大小(含它自己)
- 标记每个非叶子节点的重儿子编号 \(son\)
dfs2干的事:
- 标记每个点的新编号
- 赋值每个点的初始值到新编号上
- 处理每个点所在链的顶端
- 处理每条链
void dfs1(int x,int f,int deep){
dep[x]=deep,fa[x]=f,siz[x]=1;
int maxson=-1;//重儿子的子树大小
for(int i=h[x];i;i=nxt[i]){
int y=ver[i];
if(y==f) continue;
dfs1(y,x,deep+1);
siz[x]+=siz[y];
if(siz[y]>maxson) son[x]=y,maxson=siz[y];
}
}
void dfs2(int x,int topf){
id[x]=++cnt,wt[cnt]=w[x],top[x]=topf;
if(!son[x]) return;
dfs2(son[x],topf);
for(int i=h[x];i;i=nxt[i]){
int y=ver[i];
if(y==son[x] || y==fa[x]) continue;
dfs2(y,y);
}
}
树上操作:
//以下树上操作
int ask_range(int l,int r){
int ans=0;
while(top[l]!=top[r]){//两点不在同一条链上
if(dep[top[l]]<dep[top[r]]) swap(l,r);
ans=(ans+ask(1,1,n,id[top[l]],id[l]))%p;
l=fa[top[l]];
}
if(dep[l]>dep[r]) swap(l,r);
ans=(ans+ask(1,1,n,id[l],id[r]))%p;
return ans;
}
void change_range(int l,int r,int x){
x%=p;
while(top[l]!=top[r]){//基本同上
if(dep[top[l]]<dep[top[r]]) swap(l,r);
change(1,1,n,id[top[l]],id[l],x);
l=fa[top[l]];
}
if(dep[l]>dep[r]) swap(l,r);
change(1,1,n,id[l],id[r],x);
}
完整代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,p;
int e,h[N],nxt[N],ver[N],w[N],wt[N];
// 输入的节点值 ↑ ↑新编号后节点值
int tr[N<<2],lz[N<<2],len[N<<2];
int son[N],id[N],fa[N],cnt,dep[N],siz[N],top[N];
// ↑重儿子编号
void add(int a,int b){
ver[++e]=b,nxt[e]=h[a],h[a]=e;
}
//线段树
void push(int node,int x){
tr[node]+=x*len[node]%p;tr[node]%=p;
lz[node]+=x;lz[node]%=p;
}
void pushup(int node){
tr[node]=(tr[node<<1]+tr[node<<1|1])%p;
}
void pushdown(int node){
if(lz[node]){
push(node<<1,lz[node]);
push(node<<1|1,lz[node]);
lz[node]=0;
}
}
void build(int node,int l,int r){
len[node]=r-l+1;
if(l==r){
tr[node]=wt[l]%p;
return;
}
int mid=(l+r)>>1;
build(node<<1,l,mid);
build(node<<1|1,mid+1,r);
pushup(node);
}
int ask(int node,int l,int r,int be,int en){
if(r<be || l>en) return 0;
if(l>=be && r<=en) return tr[node];
pushdown(node);
int mid=(l+r)>>1;
return (ask(node<<1,l,mid,be,en)+ask(node<<1|1,mid+1,r,be,en))%p;
}
void change(int node,int l,int r,int be,int en,int x){
if(r<be || l>en) return;
if(l>=be && r<=en){
push(node,x);
return;
}
pushdown(node);
int mid=(l+r)>>1;
change(node<<1,l,mid,be,en,x);
change(node<<1|1,mid+1,r,be,en,x);
pushup(node);
}
//---------分割线----------
//以下树上操作
int ask_range(int l,int r){
int ans=0;
while(top[l]!=top[r]){//两点不在同一条链上
if(dep[top[l]]<dep[top[r]]) swap(l,r);
ans=(ans+ask(1,1,n,id[top[l]],id[l]))%p;
l=fa[top[l]];
}
if(dep[l]>dep[r]) swap(l,r);
ans=(ans+ask(1,1,n,id[l],id[r]))%p;
return ans;
}
void change_range(int l,int r,int x){
x%=p;
while(top[l]!=top[r]){//基本同上
if(dep[top[l]]<dep[top[r]]) swap(l,r);
change(1,1,n,id[top[l]],id[l],x);
l=fa[top[l]];
}
if(dep[l]>dep[r]) swap(l,r);
change(1,1,n,id[l],id[r],x);
}
//----------分割线-----------
/*
dfs1干的事:
- 标记每个点的深度dep[]
- 标记每个点的父亲fa[]
- 标记每个非叶子节点的子树大小(含它自己)
- 标记每个非叶子节点的重儿子编号son[]
*/
void dfs1(int x,int f,int deep){
dep[x]=deep,fa[x]=f,siz[x]=1;
int maxson=-1;//重儿子的子树大小
for(int i=h[x];i;i=nxt[i]){
int y=ver[i];
if(y==f) continue;
dfs1(y,x,deep+1);
siz[x]+=siz[y];
if(siz[y]>maxson) son[x]=y,maxson=siz[y];
}
}
/*
dfs2干的事:
- 标记每个点的新编号
- 赋值每个点的初始值到新编号上
- 处理每个点所在链的顶端
- 处理每条链
*/
void dfs2(int x,int topf){
id[x]=++cnt,wt[cnt]=w[x],top[x]=topf;
if(!son[x]) return;
dfs2(son[x],topf);
for(int i=h[x];i;i=nxt[i]){
int y=ver[i];
if(y==son[x] || y==fa[x]) continue;
dfs2(y,y);
}
}
int main(){
int root;
cin>>n>>m>>root>>p;
for(int i=1;i<=n;++i) cin>>w[i];
for(int i=1;i<n;++i){
int x,y;
cin>>x>>y;
add(x,y),add(y,x);
}
dfs1(root,0,1);
dfs2(root,root);
build(1,1,n);
while(m--){
int opt,x,y,z;
cin>>opt;
if(opt==1){
cin>>x>>y>>z;
change_range(x,y,z);
}
if(opt==2){
cin>>x>>y;
cout<<ask_range(x,y)<<endl;
}
if(opt==3){
cin>>x>>z;
change(1,1,n,id[x],id[x]+siz[x]-1,z);
}
if(opt==4){
cin>>x;
cout<<ask(1,1,n,id[x],id[x]+siz[x]-1)<<endl;
}
}
return 0;
}