树上差分
差分数组
我们令\(a[i]\)表示原数组
\(dif[i]\)表示差分数组
那么我们就有
那么\(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;
这个时候将每条边的权值给点,就是每条边的权值给其下方的点