【笔记】树上差分
树上差分
类比一维差分,用于处理树上一段路径的区间修改操作。
点差分
Luogu P3128 [USACO15DEC] Max Flow P
一点的点权可采用前缀和思想,由其子树求得。因此考虑在树上差分,定义差分数组 \(f\),则 \(w_u=f_u+\sum w_v\)。因此将树上一条深度单调的路径上的点权同时修改,只需要将深度最大点的 \(f+k\),深度最小的父节点的 \(f-k\)。
考虑树上两个端点 \(s,t\) 间的路径,可被拆分为 \(s\rightarrow \operatorname{lca}(s,t),t\rightarrow \operatorname{lca}(s,t)\)。分别对这两条路径做树上差分。注意减掉重复的 \(\operatorname{lca}(s,t)\)
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=5e4+10;
struct Node{
int to,nxt;
}e[2*maxn];
int n,k,D,ans;
int h[maxn],tot,w[maxn];
int fa[maxn][20],dep[maxn];
void Add(int u,int v){
tot++;
e[tot].to=v;
e[tot].nxt=h[u];
h[u]=tot;
}
void Dfs(int root,int cur,int fath){
fa[root][0]=fath;
D=max(D,cur);
dep[root]=cur;
for(int i=1;i<=log(dep[root])/log(2);i++){
fa[root][i]=fa[fa[root][i-1]][i-1];
}
for(int i=h[root];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fath) Dfs(v,cur+1,root);
}
}
int LCA(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
for(int i=log(D)/log(2);i>=0;i--){
if(dep[fa[a][i]]>=dep[b]) a=fa[a][i];
}
if(a==b) return a;
for(int i=log(D)/log(2);i>=0;i--){
if(fa[a][i]!=fa[b][i]){
a=fa[a][i];
b=fa[b][i];
}
}
return fa[a][0];
}
int dfs(int root,int fath){
for(int i=h[root];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fath) w[root]+=dfs(v,root);
}
return w[root];
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n-1;i++){
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);Add(y,x);
}
Dfs(1,1,0);
for(int i=1;i<=k;i++){
int s,t,l;
scanf("%d%d",&s,&t);
w[s]++,w[t]++;
l=LCA(s,t);
w[l]--,w[fa[l][0]]--;
}
dfs(1,0);
for(int i=1;i<=n;i++){
ans=max(ans,w[i]);
}
printf("%d\n",ans);
return 0;
}
Luogu P11967 [GESP202503 八级] 割裂
稍作转化。每个好点对间路径上的点都不能删除,因此考虑树上差分,为每个好点对间的点增加点权。最后点权仍为 \(0\) 的点均不影响好点对间联通,统计 \(b_u\rightarrow b_v\) 上点权为 \(0\) 的点个数即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int M=1e5+10;
int n,m,D,ans;
int h[N],tot;
int fa[N][20],dep[N],dif[N];
struct Node{
int to,nxt;
}e[2*N];
void Add(int u,int v){
tot++;
e[tot].to=v;
e[tot].nxt=h[u];
h[u]=tot;
}
void dfs1(int u,int cur,int fath){
dep[u]=cur;
D=max(D,dep[u]);
fa[u][0]=fath;
for(int i=1;i<=log(dep[u])/log(2);i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fath) dfs1(v,cur+1,u);
}
}
void dfs2(int u,int fath){
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fath) continue;
dfs2(v,u);
dif[u]+=dif[v];
}
}
int LCA(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
for(int i=log(D)/log(2);i>=0;i--){
if(dep[fa[a][i]]>=dep[b]) a=fa[a][i];
}
if(a==b) return a;
for(int i=log(D)/log(2);i>=0;i--){
if(fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
}
return fa[a][0];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
Add(u,v),Add(v,u);
}
dfs1(1,1,0);
for(int i=1;i<=m;i++){
int u,v,fuv;
cin>>u>>v;
fuv=LCA(u,v);
dif[u]++,dif[fa[fuv][0]]--;
dif[v]++,dif[fa[fuv][0]]--;
}
dfs2(1,0);
int bu,bv;
cin>>bu>>bv;
if(dep[bu]<dep[bv]) swap(bu,bv);
while(dep[bu]>dep[bv]){
if(!dif[bu]) ans++;
bu=fa[bu][0];
}
while(bu!=bv){
if(!dif[bu]) ans++;
if(!dif[bv]) ans++;
bu=fa[bu][0],bv=fa[bv][0];
}
if(!dif[bu]) ans++;
cout<<ans;
return 0;
}
边差分
维护边权的差分。直接在边上差分比较困难,可以将边权下放到子节点,除根节点以外每个节点的点权都对应该点与其父亲间边的边权。其余同点差分。
Luogu P10931 闇の連鎖
注意到主要边把点连成了一棵树,不妨考虑树上每条边对答案的贡献。
我们考虑一条附加边 \(i\) 连接的两点 \(u_i,v_i\),\(u_i\rightarrow v_i\) 上的每个主要边 \(j\) 都可以通过删除 \(i,j\) 两边使 \(u_i,v_i\) 不联通。但若 \(u_i,v_i\) 间还有其他附加边 \(k\),则 \(u_k\rightarrow v_k\) 间的主要边在与 \(k\) 组合删除后,依然会被 \(i\) 联通,因此 \(u_k\rightarrow v_k\) 上的主要边无论与哪个附加边组合删除都无法使图不联通。
因此考虑树上差分,对于每个附加边 \(i\) 将 \(u_i\rightarrow v_i\) 上的所有主要边的边权 \(+1\)。最终统计每个主要边的边权,若为 \(0\),则该主要边不在被附加边连接的点间,与任意一附加边组合删除都是合法方案,答案 \(+m\);若为 \(1\),则该边可与唯一的附加边组合删除,答案 \(+1\);若大于 \(1\),则是上述不合法情况,答案不变。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int M=2e5+10;
int n,m,ans;
int h[N],tot;
int fa[N][20],dep[N],D;
int dif[N];
struct Node{
int to,nxt;
}e[2*N];
void Add(int u,int v){
tot++;
e[tot].to=v;
e[tot].nxt=h[u];
h[u]=tot;
}
void dfs1(int u,int cur,int fath){
dep[u]=cur;
D=max(D,cur);
fa[u][0]=fath;
for(int i=1;i<=log(dep[u])/log(2);i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=fath) dfs1(v,cur+1,u);
}
}
int LCA(int a,int b){
if(dep[a]<dep[b]) swap(a,b);
for(int i=log(D)/log(2);i>=0;i--){
if(dep[fa[a][i]]>=dep[b]) a=fa[a][i];
}
if(a==b) return a;
for(int i=log(D)/log(2);i>=0;i--){
if(fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i];
}
return fa[a][0];
}
void dfs2(int u,int fath){
for(int i=h[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fath) continue;
dfs2(v,u);
dif[u]+=dif[v];
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n-1;i++){
int u,v;
cin>>u>>v;
Add(u,v),Add(v,u);
}
dfs1(1,1,0);
for(int i=1;i<=m;i++){
int u,v,l;
cin>>u>>v;
l=LCA(u,v);
dif[u]++,dif[v]++,dif[l]-=2;
}
dfs2(1,0);
for(int i=2;i<=n;i++){
if(dif[i]==1) ans++;
else if(dif[i]==0) ans+=m;
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号