【题目来源】 https://www.luogu.com.cn/problem/P3128 【题目描述】 Farmer John 在他的谷仓中安装了 N-1 条管道,用于在 N 个牛棚之间运输牛奶(2≤N≤50000),牛棚方便地编号为 1…N。每条管道连接一对牛棚,所有牛棚通过这些管道相互连接。 FJ 正在 K 对牛棚之间泵送牛奶(1≤K≤100000)。对于第 i 对牛棚,你被告知两个牛棚 si 和 ti,这是牛奶以单位速率泵送的路径的端点。FJ 担心某些牛棚可能会因为过多的牛奶通过它们而不堪重负,因为一个牛棚可能会作为许多泵送路径的中转站。请帮助他确定通过任何一个牛棚的最大牛奶量。如果牛奶沿着从 si 到 ti 的路径泵送,那么它将被计入端点牛棚 si 和 ti,以及它们之间路径上的所有牛棚。 【输入格式】 输入的第一行包含 N 和 K。 接下来的 N-1 行每行包含两个整数 x 和 y(x≠y),描述连接牛棚 x 和 y 的管道。 接下来的 K 行每行包含两个整数 s 和 t,描述牛奶泵送路径的端点牛棚。 【输出格式】 输出一个整数,表示通过谷仓中任何一个牛棚的最大牛奶量。 【输入样例】 5 10 3 4 1 5 4 2 5 4 5 4 5 4 3 5 4 3 4 3 1 3 3 5 5 4 1 5 3 4 【输出样例】 9 【数据范围】 2≤N≤5×10^4,1≤K≤10^5。 【算法分析】 ● 树上差分 树上差分是一种在树上高效处理路径修改和查询的算法技巧,核心思想是将路径操作转化为对节点差分数组的单点修改,最后通过一次遍历还原出结果。 它特别适合处理多次对树上路径进行加减操作,最后查询某个点或边的权值这类问题。 (一)核心思想 点差分:对路径 (x, y) 上所有点的权值进行修改。通过在 x 和 y 处 +val,在 lca(x,y) 和其父节点处 -val,最后通过 DFS 自底向上求和即可还原路径上的权值。具体操作为:对路径 (x, y) 加 val,执行 diff[x] += val, diff[y] += val, diff[lca] -= val, diff[fa[lca]] -= val。 边差分:对路径 (x, y) 上所有边的权值进行修改。通常将边权下放给深度较大的子节点,转化为点权问题,处理方式与点差分类似。具体操作为:对路径 (x, y) 加 val,执行 diff[x] += val, diff[y] += val, diff[lca] -= 2 * val(假设边权下放给子节点)。 (二)适用场景 点差分:路径点权修改、子树点权修改、查询点权。 边差分:路径边权修改、查询边权。 (三)与其他算法对比 与线段树对比:树上差分代码简洁,适合离线操作;线段树支持在线查询,但常数较大。 与树链剖分对比:树上差分处理路径修改更高效;树链剖分功能更强大,支持复杂路径查询。 ● LCA ← 树上差分常用 LCA (1)暴力法(向上标记法):https://blog.csdn.net/hnjzsyjyj/article/details/152026341 (2)暴力法(同步前进法):https://blog.csdn.net/hnjzsyjyj/article/details/152070927 (3)倍增法(DFS预处理):https://blog.csdn.net/hnjzsyjyj/article/details/152203103 (4)倍增法(BFS预处理):https://blog.csdn.net/hnjzsyjyj/article/details/152234376 【算法代码】 本代码中的 dfs1 + getLCA 是倍增法求 LCA 的代码。详见:https://blog.csdn.net/hnjzsyjyj/article/details/152203103 之后,dfs2 通过后序遍历完成树形差分的合并,将差分标记转化为每条边的实际覆盖次数。也就是说,dfs2 解决 “次数怎么算”的问题。
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;
const int LOG=20;
int f[N][LOG+5],dep[N],d[N];
vector<int> g[N];
int n,m,ans;
void dfs1(int u,int fa) { //preprocess
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1; (1<<i)<=dep[u]; i++) { //i<=LOG
f[u][i]=f[f[u][i-1]][i-1];
}
for(auto j:g[u]) {
if(j!=fa) dfs1(j,u);
}
}
int getLCA(int u,int v) {
if(dep[u]<dep[v]) swap(u,v);
for(int i=LOG; i>=0; i–) {
if(dep[f[u][i]]>=dep[v]) u=f[u][i];
}
if(u==v) return u;
for(int i=LOG; i>=0; i–) {
if(f[u][i]!=f[v][i]) {
u=f[u][i],v=f[v][i];
}
}
return f[u][0];
}
void dfs2(int u,int fa) {
for(auto j:g[u]) {
if(j!=fa) dfs2(j,u),d[u]+=d[j];
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1; i<n; i++) {
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs1(1,-1);
while(m–) {
int x,y,lca;
cin>>x>>y;
lca=getLCA(x,y);
d[x]++,d[y]++;
d[lca]–,d[f[lca][0]]–;
}
dfs2(1,-1);
for(int i=1; i<=n; i++) {
ans=max(ans,d[i]);
}
cout<<ans<<endl;
return 0;
}
/*
in:
5 10
3 4
1 5
4 2
5 4
5 4
5 4
3 5
4 3
4 3
1 3
3 5
5 4
1 5
3 4
out:
9
*/
【参考文献】 https://www.bilibili.com/video/BV1bg4y127QH https://blog.csdn.net/hnjzsyjyj/article/details/157199762 https://blog.csdn.net/hnjzsyjyj/article/details/157245297 https://www.cnblogs.com/Chase-s/p/10410265.html https://www.cnblogs.com/hetailang/p/16216504.html https://blog.51cto.com/u_13536312/5371363 https://www.acwing.com/solution/content/186355/



