数列差分
定义c[i]=a[i]-a[i-1],特殊的c[1]=a[1],c[n+1]=-a[n]。
常用性质
- c[1]+...+c[n+1]的和为0。
- c[i]求前缀和会得出原始数组。
- 把原数组从 l 到 r 都加val,那么差分数组相当于c[l]+=val,c[r+1]-=val。
树上差分
点差分
设将树上两点A,B两点的简单路径上的点都加val。
- 将A到B的路径拆为A到LCA和B到LCA。
- 将A到LCA的路径看作一个数列,区间加val等价于c[A]+=val,c[fa[LCA]]-=val。
- 同理,c[B]+=val,c[LCA]-=val。
边差分
相当于把边权放到连接的儿子节点上,那么就转换成c[A]+=val,c[B]+=val,c[LCA]-=val*2。
典型例题
P3128 [USACO15DEC] Max Flow P
板板板。
首先把LCA预处理出来,然后点差分,最后用一次dfs前缀和,求最大值输出完事。
cpp
int ans;
void dfs1(int x,int fa){
for(int i=0;i<E[x].size();i++){
int v=E[x][i];
if(v==fa)continue;
dfs1(v,x);
c[x]+=c[v];
}
ans=max(ans,c[x]);
}
int main(){
while(k--){
int x,y;//输入要树上差分的两个结点
cin>>x>>y;
int lca=LCA(x,y);//求出LCA
c[x]++;//差分
c[y]++;
c[lca]--;
c[fc[0][lca]]--;
}
dfs1(1,0);//前缀和
cout<<ans;
return 0;
}
P3258 [JLOI2014] 松鼠的新家
典型的一道树上差分的题,首先把LCA预处理出来,然后在a数组里找到每个两两相邻的点作树上差分,差分完求前缀和,最后因为枚举相邻点时中间节点会多放一个,且最后一个节点已经到了厨房会多放一个,所以前缀和完了后这几个节点都要减1。
cpp
void dfs1(int x,int fa){//将差分数组前缀和,得出原本的数组
for(int i=0;i<E[x].size();i++){
int v=E[x][i];
if(v==fa)continue;
dfs1(v,x);
c[x]+=c[v];
}
}
int main(){
for(int i=2;i<=n;i++){
int x=a[i-1],y=a[i];//将每两个相邻的点做一次树上差分
int lca=LCA(x,y);
c[x]++;
c[y]++;
c[lca]--;//点差分
c[fc[0][lca]]--;
}
dfs1(1,0);
for(int i=2;i<=n;i++){//减1大法
c[a[i]]--;
}
}
U143800 暗之连锁
题意:Duck 是人类内心的黑暗的产物,今古外中的勇者们都试图打倒它。
放错了:给定n个点的树有n-1条主要边,还有m条附加边,要求删除1条主要边和1条附加边,使得不连通,求方案数。
- 对于一条主要边(树边),若其不在环上,则第二条边随便删,共m种方法。
- 一条主要边恰好在一条环上,则第二条边选择唯一,贡献一种方法。
- 否则一条主要边在两条环以上删一条不够,贡献为0。
那么问题就转化成统计主要边在多少个环上,对于一条附加边(x,y),添加后会在树上x到y的主要边路径形成环,那么问题又变成了路径覆盖问题,考虑边差分。
cpp
void dfs1(int x,int fa){
for(int i=0;i<E[x].size();i++){
int v=E[x][i];
if(v==fa)continue;
dfs1(v,x);
c[x]+=c[v];
}
}
int main(){
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
int lca=LCA(x,y);
c[x]++;
c[y]++;
c[lca]-=2;//边差分
}
dfs1(1,0);//前缀和
int ans=0;
for(int i=2;i<=n;i++){//边差分,不算根节点
if(c[i]==0){//没在任一环上
ans+=m;
}
if(c[i]==1){//恰好在一环上
ans++;
}
}
cout<<ans;
return 0;
}