树上差分

差分数组

我们令\(a[i]\)表示原数组
\(dif[i]\)表示差分数组
那么我们就有

\[\left\{ \begin{aligned} dif[0]&=a[0]\\ dif[i]&=a[i]-a[i-1],i>0 \end{aligned} \right. \]

那么\(a[i]=\sum_{k=0}^idif[k]\)

a[i] 4 6 4 3 6
dif[i] 4 2 -2 -1 -3

假设我们要将a[i]到a[k]的值全部加1,那么在差分数组中只需要++a[i],--a[k+1]。

树上差分

树上差分有两种,一种是点差分,另一种是边差分。

点差分

现在我们有如下一棵树

仍然是a[i]表示i结点的权值,dif[i]表示差分数组。
对应到树中就有dif[8]=a[8],dif[5]=a[5]-a[8],dif[4]=a[4]-a[5]-a[6]-a[7]
那么在dfs回溯时加上一句dif[i]+=dif[to] (to为i的子节点),dif[i]就转化为原数组了。
就是说,结点u的值 = u所有子节点的值+u差分数组的值
对比普通的差分数组,不同之处就在于树上的结点有多个子结点,而数组只有一个直接前驱。
路径修改
假设我们要将8-->10这条路径的所有点的权值加x

我们只用++dif[8],++dif[10],--dif[lca(8,10)] (就是4号结点),--dif[father[lca(8,10)]] (就是2号结点),就可以了。

这里我们可以看成先将4->8这条链加1,再将4->10这条链加1,由于4号点被加了两次,再减1即可。
注意回溯(就是从差分数组到原数组)的过程是倒过来的,即从下向上,所以+1的点在下面,-1的点在上面

例题
P3128 [USACO15DEC]Max Flow P
参考代码

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
struct{
	int to,next;
}e[2*N];
int head[N],cnt;
int f[N][30],lg[N],dep[N];
int n,k,power[N];
inline void add(int a,int b){
	e[++cnt].to=b;
	e[cnt].next=head[a];
	head[a]=cnt;
}
void dfs(int a,int fa){
	dep[a]=dep[fa]+1;
	f[a][0]=fa;
	for(int i=1;i<=lg[dep[a]];++i)f[a][i]=f[f[a][i-1]][i-1];
	int to;
	for(int i=head[a];i;i=e[i].next){
		to=e[i].to;
		if(to==fa)continue;
		dfs(to,a);
	}
}
int lca(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	int d=dep[a]-dep[b];
	int t=0;
	while(d){
		if(d&(1<<t))a=f[a][t],d-=(1<<t);
		++t;
	}
	if(a==b)return b;
	for(int i=lg[dep[a]];i>=0;--i){
		if(f[a][i]!=f[b][i]){
			a=f[a][i];
			b=f[b][i];
		}
	}
	return f[a][0];
}
int ans;
void get(int a,int fa){
	int to;
	for(int i=head[a];i;i=e[i].next){
		to=e[i].to;
		if(to==fa)continue;
		get(to,a);
		power[a]+=power[to];//将差分数组变为原数组
	}
	ans=max(ans,power[a]);
}
int main(){
	scanf("%d%d",&n,&k);
	int a,b;
	for(int i=2;i<=n;++i)lg[i]=lg[i-1]+(1<<lg[i-1]+1==i);
	for(int i=1;i<n;++i){
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	dfs(1,0);
	for(int i=1;i<=k;++i){
		scanf("%d%d",&a,&b);
		int l=lca(a,b);
		++power[a],++power[b],--power[l],--power[f[l][0]];//区间修改
	}
	get(1,0);
	printf("%d",ans);
	return 0;
}

边差分

将边的权值给子结点。

仍然是刚刚那条路经,由于修改的是边,权值给结点后将不包含lca(8,10)(4号结点)
那这个时候修改的操作为
++dif[8],++dif[10],dif[lca(8,10]-=2;

这个时候将每条边的权值给点,就是每条边的权值给其下方的点

posted @ 2022-05-02 19:53  何太狼  阅读(33)  评论(0编辑  收藏  举报