树上点差分
https://www.luogu.com.cn/problem/P3128
题意理解
-
一棵树,根节点为1
-
大量修改操作,但是每次是将两个点之间的路径上的所有点,都增加1
-
一个查询操作,问最后哪一个点上的值最大。
算法解析
使用树上差分+最近公共祖先即可。
就是一个点上的最后权值,就是统计子树和的过程。
树上差分
举个例子:有一个树。设原树如下,现要将2,3之间路径上的所有点的权值增加3,设原权值均为0。可以求任一个点的权值。
现在有两个想法:
- 暴力:一个是将2,3路径上的所有权值,都+3
- 差分:一个是对路径上的重要节点进行修改(而不是暴力全改),作为其差分数组的值,最后在求值时,利用dfs遍历求出差分数组的前缀和。一个点上的最后权值,就是统计子树和的过程。
我们现在来看看第二种方式:
树上点差分
设将两点u,v之间路径上的所有点权增加x,o=LCA(u,v),o的父亲节点为p,则操作如下:
diff[u]+=x,diff[v]+=x,diff[o]-=x,diff[p]-=x;
如下图:
差分后:
此时,我们可以求任意节点的权值。
任意节点的权值 = 自己和其子树的差分权值之和。
例如:4号节点 = -3+ 3 + 3 =3
3号节点 = 3
5号节点 = 0。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4+10;
int n,q;
int h[N],e[N*2],ne[N*2],idx;
int fa[N][22];
int deep[N];//记录深度
int lg[N];
int p[N];//差分数组
void add(int x, int y){
e[idx] = y; ne[idx] = h[x]; h[x] = idx++;
}
//填写上fa数组
void dfs1(int now , int fath){//now表示当前节点,fath表示它的父亲节点
fa[now][0] = fath; deep[now] = deep[fath] +1;
for (int i = 1; i <= lg[deep[now]]; ++i) {
//根据递推公式 fa(i,j) = fa(fa(i,j-1),fa(i,j-1))
fa[now][i] = fa[fa[now][i-1]][i-1];
}
for (int i = h[now]; i != -1 ; i = ne[i]) {
int j = e[i]; // now的所指向的节点
if( j != fath) dfs1(j,now); //如果所指节点不是父节点,那么遍历子节点
}
}
//求最短公共祖先
int lca(int x, int y){
if(deep[x] < deep[y]) swap(x,y); //保证x的深度是最大的
// x,y争取跳到同一层
while (deep[x] > deep[y]) x = fa[x][lg[deep[x] - deep[y]]];
if(x == y) // 如果其中一个节点是另一个父节点,那么直接返回
return x;
for (int i = lg[deep[x]]; i >=0 ; --i) { // x,y现在在同一层了,那么一起跳
if(fa[x][i] != fa[y][i]) x = fa[x][i],y = fa[y][i]; //一直调到公共祖先下一层
}
return fa[x][0];
}
void dfs2(int u, int fath) {
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if(j != fath){
dfs2(j,u);
p[u] += p[j];//累计权值
}
}
}
int main(){
memset(h,-1, sizeof(h));
//输入
cin >> n >> q;
//建图
for (int i = 0; i < n-1; ++i) {
int x,y;
cin >> x >> y;
add(x,y);add(y,x);
}
//给lg打表
lg[0] = -1;
for (int i = 1; i <= n ; ++i) {
lg[i] = lg[i>>1] + 1;
}
dfs1(1,0);
while(q--){
int a, b;
cin >> a >> b;
//输出下lca的结果
// cout << "lca:"<< lca(a,b) << endl;
int lc = lca(a,b);
p[lc]--;
p[fa[lc][0]]--;
p[a]++;
p[b]++;
}
dfs2(1,0);//累计权值,完成差分数组
int res = 0;
for (int i = 1; i <= n ; ++i) {
res = max(res,p[i]);
}
cout << res;
return 0;
}