洛谷 P3128:[USACO15DEC] Max Flow P ← 树上差分(点差分)

【题目来源】
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/
 

posted @ 2026-01-22 09:55  Triwa  阅读(0)  评论(0)    收藏  举报