Loading

【笔记】树上差分

树上差分

类比一维差分,用于处理树上一段路径的区间修改操作。

点差分

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;
}
posted @ 2025-12-23 17:21  Seqfrel  阅读(2)  评论(0)    收藏  举报