树上差分
树上差分
定义
树上差分就是树上的差分——gzy
用途(差分)
它可以维护多次对序列的一个区间加上一个数,并在最后询问某一位的数或是多次询问某一位的数。(总之修改操作一定要在查询操作之前)
点差分
对于树上的区间(假设为 [l,r][l,r]),我们可以把它看成从 ll走到 rr的简单路径
而前缀和就可以看成是每个点的子树和(包括该点)
核心代码:
for(int i=1;i<=k;i++){ int a,c; cin>>a>>c; int lc=lca(a,c);//计算出a,c的最近公共祖先,以便处理a,c的简单路径 b[a]++; b[f[lc][0]]--;//消除a带来的影响 b[c]++; b[lc]--;//lc这个点实际上会因为a,c加两次,而实际上他只需要加一次,所以减掉一次 }
求前缀和:
void dfs(int x,int fa){ //cout<<x<<endl; for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(y==fa) continue; dfs(y,x); b[x]+=b[y];//所有子树的和 } return ; }
边差分
将边的差分存在点中(子节点,因为具有唯一性)
需要注意的是,修改的方法和点差分有些许不同
代码解释:
for(int i=1;i<=m;i++){ int a,c; cin>>a>>c; int lc=lca(a,c); b[a]++; b[c]++; b[lc]-=2;//这里b[lc]就要减两次了,因为b[lc]这条边并不在我们期望的路径中,b[f[lc][0]]就更不用管了 }
来一道例题:https://www.luogu.com.cn/problem/P3128
AC代码:
#include<bits/stdc++.h> #include<queue> using namespace std; const int N=3e5; int n,k; int ord[N]; int ver[2*N],head[N],nex[2*N]; int d[N], f[N][25]; int b[N]; int idx=1; queue<int>q; int t; void add(int x,int y){ ver[++idx]=y; nex[idx]=head[x]; head[x]=idx; } void bfs(){ q.push(1); d[1]=1; while(!q.empty()){ int x=q.front(); q.pop(); for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(d[y]) continue; d[y]=d[x]+1; f[y][0]=x; for(int j=1;j<=t;j++){ f[y][j]=f[f[y][j-1]][j-1]; } q.push(y); } } return ; } void dfs(int x,int fa){ //cout<<x<<endl; for(int i=head[x];i;i=nex[i]){ int y=ver[i]; if(y==fa) continue; dfs(y,x); b[x]+=b[y]; } return ; } int lca(int x,int y){ if(d[x]<d[y]) swap(x,y); for(int i=t;i>=0;i--){ if(d[f[x][i]]>=d[y]) x=f[x][i]; } if(x==y) return x; for(int i=t;i>=0;i--){ if(f[x][i]!=f[y][i]){ x=f[x][i]; y=f[y][i]; } } return f[x][0]; } int main(){ cin>>n>>k; t=(int)(log(n)/log(2))+1; for(int i=1;i<n;i++){ int x,y; cin>>x>>y; add(x,y); add(y,x); } bfs(); for(int i=1;i<=k;i++){ int a,c; cin>>a>>c; int lc=lca(a,c); b[a]++; b[c]++; b[lc]--; b[f[lc][0]]--; } dfs(1,0); int maxn=-1; for(int i=1;i<=n;i++){ maxn=max(maxn,b[i]); } cout<<maxn; return 0; }