树剖总结
树剖维护的是树上的链的信息,支持 O ( log n ) O(\log n) O(logn) 的区间修改,区间查询。相当于把一棵树压缩成若干链,然后达到快速跳跃的目的(常数比倍增小很多)。
维护以下信息:
f a u fa_u fau: u u u 的父亲。
s z u sz_u szu: u u u 子树的大小。
d u d_u du: u u u 的深度。
s o n u son_u sonu: u u u 的重儿子
(一个点的重儿子,为其儿子中子树大小最大的,如有多个则取第一个)
t o p u top_u topu: u u u 所在链的链头。
d f n C n t dfnCnt dfnCnt:时间戳,
d f n u dfn_u dfnu:DFS 过程中访问到 u u u 的时间。
r n k x rnk_x rnkx:可以理解为 d f n dfn dfn 的反数组:即若 d f n u = x dfn_u=x dfnu=x,则 r n k x = u rnk_x=u rnkx=u。(这个可以不维护的)
方法:两次 DFS
DFS 1:维护 s z , f a , d , s o n sz,fa,d,son sz,fa,d,son。
DFS 2:重点来了(敲黑板)。这里首先是这样的:
- 我们遍历每条重链,然后标记他的链头 t p tp tp。
- 对于每个 u u u,标记链头 t o p u = t p top_u=tp topu=tp。
- 更新 d f n , r n k dfn,rnk dfn,rnk。
- 若该点没有重儿子,退出 DFS
- 否则,先走重儿子,链头还是 t p tp tp。
- 之后,遍历 { v ∣ u → v ∈ E } \{v|u\to v\in E\} {v∣u→v∈E} 且 v ≠ f a u , v ≠ s o n u v\ne fa_u,v\ne son_u v=fau,v=sonu。
- 那么这些链的链头,就是 v v v 了。
好的,有了以上的流程,所有信息都预处理出来了。之后就可以乱搞了(bushi
求 LCA:如果 u u u 所在链头不同于 v v v 所在链头,我们重复以下流程:
- 把链头深度深的,我们将他变成链头的父亲。
好,那么之后 t o p u = t o p v top_u=top_v topu=topv 了,我们发现 u , v u,v u,v 中,深度较浅的,就是 LCA。
那么还有两点需要处理,就是,
- 我如果想要遍历 u → v u\to v u→v 路径上所有点,并且更新信息,该怎么做。
我们有一个观察:就是 t o p u → u top_u\to u topu→u 的 d f n dfn dfn 时间是连续的。基于此我们有以下流程:
- 如果 t o p u ≠ t o p v top_u\ne top_v topu=topv,我们重复以下流程:
- 首先指定 u u u 为链头深度大的, v v v 为另一个。
- 那么 d f n t o p u ∼ d f n u dfn_{top_u}\sim dfn_u dfntopu∼dfnu 就是要处理的区间。
我们现在有 t o p u = t o p v top_u=top_v topu=topv 了,我们发现,深度浅的到深度深的,就是我们最后一次要处理的区间。
- 如果想修改一颗子树内的所有点的信息,该怎么做。
我们有一个观察: u u u 子树内的点构成的 d f n dfn dfn 序列,正好覆盖 [ d f n u , d f n u + s z u − 1 ] [dfn_u,dfn_u+sz_u-1] [dfnu,dfnu+szu−1] 这里面的所有时间。那么一切就都方便了。
来一道例题 P3384。
板子题不解释。
软件包管理器:P2146。
可以发现,依赖关系形成一棵树。对于 a a a 依赖 b b b,我们连一条从 b b b 向 a a a 的有向边。为了写代码方便,我们默认序号从 1 1 1 开始编号。因为我们树是定根的,所以可以。我们发现安装一个软件事实上就是改变 1 ∼ x 1\sim x 1∼x 的链上的信息。这题,我们记录所有和就行,然后算一下差值。卸载就是把一颗子树清空。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,q;
vector<int> G[N];
int dfn[N],sz[N],dfnCnt,son[N],fa[N],dep[N],top[N];
void dfs1(int u){
sz[u]=1; //dfn[u]=++dfnCnt;
for(int v:G[u]) if(v^fa[u]){
fa[v]=u;
dep[v]=dep[u]+1;
dfs1(v);
sz[u]+=sz[v];
if(sz[son[u]]<sz[v]) son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp;
dfn[u]=++dfnCnt;
if(!son[u]) return;
dfs2(son[u],tp);
for(int v:G[u])
if(v!=son[u]&&v!=fa[u])
dfs2(v,v);
}
int lzy[N<<2],sum[N<<2];
void pushdown(int h,int len){
if(!~lzy[h]) return; //lzy[h]==-1
lzy[h<<1]=lzy[h];
lzy[h<<1|1]=lzy[h];
sum[h<<1]=(len-len/2)*lzy[h];
sum[h<<1|1]=(len/2)*lzy[h];
lzy[h]=-1;
}
void pushup(int h){
sum[h]=sum[h<<1]+sum[h<<1|1];
}
#define mid (l+r>>1)
void update(int h,int l,int r,int x,int y,int z){
if(x<=l&&r<=y)
return sum[h]=z*(r-l+1),void(lzy[h]=z);
pushdown(h,r-l+1);
if(x<=mid) update(h*2,l,mid,x,y,z);
if(y>mid) update(h*2+1,mid+1,r,x,y,z);
pushup(h);
}
void updRange(int u,int v,int w){
while(top[u]!=top[v]) {
if(dep[top[u]]<dep[top[v]]) swap(u,v);
update(1,1,n,dfn[top[u]],dfn[u],w);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
update(1,1,n,dfn[u],dfn[v],w);
}
void Out(const int now,int l,int r){
if(l==r){
cout<<sum[now]<<' ';
if(r==n) cout<<endl;
return ;
}
pushdown(now,r-l+1);
Out(now<<1,l,mid);Out(now<<1|1,mid+1,r);
}
main(){
cin.tie(0)->sync_with_stdio(0);
cin>>n;
memset(lzy,-1,sizeof lzy);
for(int i=1,u;i<n;++i){
cin>>u; ++u;
if(u) G[u].push_back(i+1);
}
dfs1(1);dfs2(1,1);
cin>>q;
while(q--){
int x; char op[20];
cin>>op>>x;
++x;
int pre=sum[1];
if(*op=='i') updRange(1,x,1);
else if(*op=='u') update(1,1,n,dfn[x],dfn[x]+sz[x]-1,0);
cout<<abs(pre-sum[1])<<'\n';
// Out(1,1,n);
}
}
我们发现每条额外的边 u → v u\to v u→v,都会整合所有 u u u 到 v v v 路径上的边。也就是说,如果我断掉任意一条路径上的边,我都可以拿这条额外的边替代。于是我们就有了一个区间取 min \min min 的做法。用 d f n u dfn_u dfnu 代指 f a u → u fa_u\to u fau→u 的边。于是我们修改 u u u 到 v v v 路径上的边,和上面的不同就在于最后只需要深的点,到浅的点的 s o n son son 即可。这样可以发现,也是不重不漏的。那么询问就是单点询问了,可以线段树 O ( log n ) O(\log n) O(logn) 做。询问的,事实上就是深的点的 d f n dfn dfn。
#include<bits/stdc++.h>
using namespace std;
const int N=50010;
int n,m,fa[N],sz[N],d[N],son[N],top[N],dfnCnt,dfn[N];
vector<int> G[N];
void dfs1(int u){
sz[u]=1; for(int v:G[u]) if(v^fa[u])
fa[v]=u,d[v]=d[u]+1,dfs1(v),sz[u]+=sz[v],sz[son[u]]<sz[v]&&(son[u]=v);
}
void dfs2(int u,int tp){
top[u]=tp,dfn[u]=++dfnCnt;
if(!son[u]) return;
dfs2(son[u],tp);
for(int v:G[u]) if(v!=fa[u]&&v!=son[u]) dfs2(v,v);
}
int Mn[N<<2],Lzy[N<<2];
constexpr int Inf=0x3f3f3f3f;
#define Tree int h,int l,int r
#define ls h*2
#define rs ls|1
#define mid (l+r>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
void pushup(int h){
Mn[h]=min(Mn[ls],Mn[rs]);
}
void smin(int &x,int y){x=min(x,y);}
void pushdown(int h){
if(Lzy[h]==Inf) return;
smin(Mn[ls],Lzy[h]);
smin(Mn[rs],Lzy[h]);
smin(Lzy[ls],Lzy[h]);
smin(Lzy[rs],Lzy[h]);
Lzy[h]=Inf;
}
void updRange(Tree,int x,int y,int w){
if(x<=l&&r<=y) return smin(Mn[h],w),smin(Lzy[h],w);
pushdown(h);
if(x<=mid) updRange(lson,x,y,w);if(y>mid) updRange(rson,x,y,w);
pushup(h);
}
int ask(Tree,int x){
if(l==r) return Mn[h];
pushdown(h);
if(x<=mid) return ask(lson,x);
else return ask(rson,x);
}
void update(int u,int v,int w){
for(;top[u]!=top[v];u=fa[top[u]]){
if(d[top[u]]<d[top[v]]) swap(u,v);
updRange(1,1,n,dfn[top[u]],dfn[u],w);
}
if(d[u]>d[v]) swap(u,v);
if(u==v) return;
updRange(1,1,n,dfn[/*son[u]*/u]+1,dfn[v],w);
}
int id[N],E[N];
pair<int,int> e[N];
void get(Tree){
if(l==r) return void(id[l]=h);
pushdown(h);
get(lson),get(rson);
}
main(){
cin.tie(0)->sync_with_stdio(0);
cin>>n>>m;
memset(Mn,0x3f,4*n+10<<2);
memset(Lzy,0x3f,4*n+10<<2);
for(int i=1;i<n;++i){
int u,v; cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
e[i]={u,v};
}
dfs1(1),dfs2(1,1);
for(int i=1;i<n;++i){
auto[u,v]=e[i];
if(d[u]>d[v]) E[i]=u;
else E[i]=v;
}
for(int i=1;i<=m;++i){
int u,v,w;cin>>u>>v>>w;
update(u,v,w);
}
get(1,1,n);
for(int i=1;i<n;++i){
int&ans=Mn[id[dfn[E[i]]]];
if(ans==0x3f3f3f3f) cout<<"-1\n";
else cout<<ans<<'\n';
}
}