差分|树上差分学习笔记|P3128 [USACO15DEC]最大流Max Flow题解
Sagittarius
2018-07-29 15:22:30
[【树上差分模板题】](https://www.luogu.org/problemnew/show/P3128)
矩阵的差分应该很简单,典型题目是这样的:
给你一堆数,有n次修改操作,每次给i..j这个区间加上x。最后问所有数中最大的那个。
差分,通俗一点就是把区间的操作改为点操作,在点上记录变化量。上题只需记录dlt[i]+=x,dlt[j+1]-=x,最后用前缀和扫一遍即可。
树上差分,顾名思义就是在树上搞差分。有两种典型题型,一种是边差分,一种是点差分。边差分裸题长这样:
给你一棵树,有n次修改操作,每次把u..v的路径权值加x,最后问从x..y的路径权值和。
![](https://cdn.luogu.com.cn/upload/pic/25763.png)
例如有一次操作是把红点(u)到绿点(v)之间的路径全部加x。那么我就标记dlt[u]+=x,dlt[v]+=x。
![](https://cdn.luogu.com.cn/upload/pic/25764.png)
这样在最后求解的时候,回溯的时候顺便算一下答案就出来了。
然后我们要在lca(u,v)处标记dlt[lca(u,v)]-=2x。这样就使得加x的效果只局限在u..v,不会向lca(u,v)的爸爸蔓延。
点差分和边差分稍有差别:
有n次修改操作,每次把u..v的所有点权都加x,最后问点权最大的为多少。这道题就是最上方链接的那题了。
做法也是和边差分稍有不同,我们不在dlt[lca(u,v)]-=2x,而是把dlt[lca(u,v)]-=x并把dlt[fa[lca(u,v)]]-=x。为什么呢?因为lca(u,v)也在u..v这条路径上,它同样需要被加x。回溯的时候会从u和v两个方向都给lca(u,v)加一个x,而它只能加一个,因此dlt[lca(u,v)]-=x。而lca(u,v)的爸爸则根本无法被加,在lca(u,v)已经只加一个x了,因此dlt[fa[lca(u,v)]]-=x就能让lca(u,v)的爸爸不加x。
具体看代码:
```
#include<bits/stdc++.h>
#define fsb(a,b,c) for (int a=b;a<=c;a++)
#define maxn 50100
#define maxm 100100
#define mem(a,b) memset(a,b,sizeof(a))
#define max(a,b) ((a)>(b)?(a):(b))
using namespace std;
struct edge{
int p,nt;
}a[maxn*2];
int n,m,x,y,head[maxn],cnt=0,cntb=0,headb[maxn],fa[maxn],vis[maxn],dlt[maxn],faa[maxn],maxx=0;
struct qus{
int p,nt,mark;
}b[maxm*2];
struct ablca{
int x,y,z;
}ans[maxm];
inline void adda(int x,int y){
a[++cnt].p=y;a[cnt].nt=head[x];head[x]=cnt;
}
inline void addb(int x,int y,int z){
b[++cntb].p=y;b[cntb].mark=z;b[cntb].nt=headb[x];headb[x]=cntb;
}
inline int getf(int x){
return x==fa[x]?x:fa[x]=getf(fa[x]);
}
inline void dfs1(int u,int f){//tarjan求lca
faa[u]=f;
for (int t=headb[u];t!=-1;t=b[t].nt){
int v=b[t].p;
if (vis[v]) ans[b[t].mark].z=getf(v);
}
// printf("%d ",u);
vis[u]=1;
for (int t=head[u];t!=-1;t=a[t].nt){
int v=a[t].p;if (v==f) continue;
dfs1(v,u);
int fau=getf(u),fav=getf(v);
if (fau!=fav) fa[fav]=fau;
}
}
inline int dfs2(int u){
int now=dlt[u],ans=0;
for (int t=head[u];t!=-1;t=a[t].nt){
int v=a[t].p;if (v==faa[u]) continue;
now+=dfs2(v);//now记录点u的修改后点权
}
maxx=max(maxx,now);
return now;
}
int main(){
// freopen("std.in","r",stdin);
scanf("%d%d",&n,&m);
mem(head,255);
fsb(i,1,n-1){
scanf("%d%d",&x,&y);adda(x,y);adda(y,x);
}
mem(headb,255);
fsb(i,1,m){
scanf("%d%d",&ans[i].x,&ans[i].y);
addb(ans[i].x,ans[i].y,i);addb(ans[i].y,ans[i].x,i);
}
fsb(i,1,n) fa[i]=i;mem(vis,0);
dfs1(1,0);mem(dlt,0);
fsb(i,1,m){
dlt[ans[i].x]++;dlt[ans[i].y]++;dlt[ans[i].z]--;dlt[faa[ans[i].z]]--;
}
// printf("!!!\n");
n=dfs2(1);
printf("%d\n",maxx);
return 0;
}
```
差分适用于修改多而询问少的情况,本题中只有一次询问,非常适合【别和我说线段树,树链剖分,LCT云云,赛场上写了1个小时调了1个小时结果炸了岂不心态要崩orz一定是我太菜了调不出来orz