序列上的差分,相当于树是一条链的情况。
d i = a i − a i − 1 ( i ≥ 2 ) d_i=a_i-a_{i-1}(i \ge 2) di=ai−ai−1(i≥2)
d 1 = a 1 d_1=a_1 d1=a1
那么对于 [ x , y ] [x,y] [x,y] 的区间加:
d x ← d x + 1 d_x \larr d_x+1 dx←dx+1
d y + 1 ← d y + 1 − 1 d_{y+1} \larr d_{y+1}-1 dy+1←dy+1−1
最终值:
a i = ∑ j = 1 i d j a_i=\sum\limits_{j=1}^{i} d_j ai=j=1∑idj
cin >> n >> k;
while(k -- ) {
cin >> x >> y;
d[x] ++ , d[y+1] -- ;
}
int mx = 0;
for(int i=1; i<=n; i++)
a[i] = a[i-1] + d[i],
mx = max(mx, a[i]);
cout << mx;
同样的我们也可以用从 n n n 开始后缀和的形式:
cin >> n >> k;
while(k -- ) {
cin >> x >> y;
d[y] ++ , d[x-1] --;
}
int mx = 0;
for(int i=n; i>=1; i--)
a[i] = a[i+1] + d[i],
mx = max(mx, a[i]);
cout << mx;
由此可见,不同的前缀和方式对应着不同的差分、求具体值方式。
题目
给定一棵树,多次操作,每次操作在树上一条简单路径上的点都加 1 1 1,问操作后所有点权最大值。
一般情况中,类比 P1,我们首先需要确定求具体值方式。
记 a u a_u au 表示结点 u u u 的点权大小。
在树上有两种求结点 u u u 的具体值方式:
自上而下:从根节点开始到 u u u 求和求具体值。
这种情况比较容易想到,每个点的差分 d u d_u du 表示的是 u u u 与其父亲的差值,即 d u = a u − a f a u d_u=a_u-a_{fa_u} du=au−afau。
这样对于单次路径的修改,我们要将 lca 处的结点 +1,并将所有路径上的点相连的非路径上的点 -1,这样修改的点数是至多 n n n 个,时间复杂度是 O ( n ) O(n) O(n) 级别的,不符合要求。
自下而上:从叶子结点到结点 u u u 求和求具体值。
此时差分表示的是 u u u 与其所有儿子 v v v 的和的差值,即 d u = a u − ∑ v ∈ s o n u a v d_u = a_u - \sum\limits_{v \in son_u} a_v du=au−v∈sonu∑av。
根据求和方式,我们考虑将 x x x 到 y y y 的路径拆分为两条链:
x → . . . → lca(x,y) x \to ... \to \text{lca(x,y)} x→...→lca(x,y) 和 lca(x,y) → . . . → y \text{lca(x,y)} \to ... \to y lca(x,y)→...→y 。(注意不要重复统计 lca \text{lca} lca 处)
这样对于单次路径修改,我们只需修改 x , y x,y x,y 以及 lca(x,y),fa[lca(x,y)] \text{lca(x,y),fa[lca(x,y)]} lca(x,y),fa[lca(x,y)] 即可,时间复杂度 O ( 1 ) O(1) O(1)。
核心代码:
void dfs2(int u, int fa)
{
a[u] = d[u];
for(auto v : G[u])
{
if(v == fa) continue;
dfs2(v, u);
a[u] += a[v];
}
mx = max(mx, a[u]);
}
int main()
{
...
while(k -- )
{
int x, y;
cin >> x >> y;
int LCA = lca(x, y);
d[x] ++ , d[LCA] -- ;
d[y] ++ , d[f[LCA][0]] -- ;
}
dfs2(1, 0);
cout << mx;
return 0;
}
给定一棵树,多次操作,每次操作在树上一条简单路径上的边都加 1 1 1,问操作后所有边权最大值。
考虑转边权为点权: a u a_u au 表示结点 u u u 与 f a u fa_u fau 间边的权值。
对于在链上的操作 x → . . . → s o n y → y x \to ... \to son_y \to y x→...→sony→y ,根据 a a a 的定义,我们应当将 x x x 到 s o n y son_y sony 之间的点的 a a a 都加 1 1 1 ,差分即 d x ← d x + 1 , d y ← d y − 1 d_x \larr d_x +1,d_y \larr d_y-1 dx←dx+1,dy←dy−1。
对于简单路径 x x x 到 y y y 的加操作,即拆分为两条链上路径 [ x , l c a ) [x,lca) [x,lca) 与 [ y , l c a ) [y,lca) [y,lca)。
...
while(k -- )
{
int x, y;
cin >> x >> y;
int LCA = lca(x, y);
d[x] ++ , d[y] ++ , d[LCA] -= 2;
}
dfs2(1, 0);
cout << mx;