AcWing 4963:砍树 ← 树上差分(边差分)+ dfs预处理

【题目来源】
https://www.acwing.com/problem/content/4966/

【题目描述】
给定一棵由 n 个结点组成的树以及 m 个不重复的无序数对(a1, b1),(a2, b2),…,(am, bm),其中 ai 互不相同,bi 互不相同,ai≠bj(1≤i, j≤m)。
小明想知道是否能够选择一条树上的边砍断,使得对于每个(ai, bi)满足 ai 和 bi 不连通,如果可以则输出应该断掉的边的编号(编号按输入顺序从 1 开始),否则输出 -1。

【输入格式】
输入共 n+m 行,第一行为两个正整数 n,m。
后面 n-1 行,每行两个正整数 xi,yi 表示第 i 条边的两个端点。
后面 m 行,每行两个正整数 ai,bi。​​​​​​​

【输出格式】
一行一个整数,表示答案,如有多个答案,输出编号最大的一个。​​​​​​​

【输入样例】
6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5

【输出样例】
4

【样例解释】
断开第 2 条边后形成两个连通块:{3,4},{1,2,5,6},满足 3 和 6 不连通,4 和 5 不连通。
断开第 4 条边后形成两个连通块:{1,2,3,4},{5,6},同样满足 3 和 6 不连通,4 和 5 不连通。
4 编号更大,因此答案为 4。

【数据范围】
对于 30% 的数据,保证 1<n≤1000。
对于 100% 的数据,保证 1<n≤10^5,1≤m≤n/2。​​​​​​​

【算法分析】
● 树上差分
树上差分是一种在树上高效处理路径修改和查询的算法技巧,核心思想是将路径操作转化为对节点差分数组的单点修改,最后通过一次遍历还原出结果。 它特别适合处理‌多次对树上路径进行加减操作,最后查询某个点或边的权值‌这类问题。
(一)核心思想
点差分‌:对路径 (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

【算法代码:dfs预处理
视频讲解详见:https://www.bilibili.com/video/BV1bg4y127QH/

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef pair<int,int> PII;
const int N=2e5+5;
const int LOG=20;
vector<int> g[N];
map<PII,int> mp;
int dep[N],f[N][LOG+5];
int d[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];
}

int dfs2(int u,int fa) {
    int res=d[u];
    for(auto j:g[u]) {
        if(j==fa) continue;
        int cnt=dfs2(j,u);
        if(cnt==m) {
            ans=max(ans,mp[{j,u}]);
        }
        res+=cnt;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin>>n>>m;
    for(int i=1; i<n; i++) {
        int u,v;
        cin>>u>>v;
        mp[{u,v}]=mp[{v,u}]=i;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs1(1,-1);

    for(int i=1; i<=m; i++) {
        int x,y;
        cin>>x>>y;
        int lca=getLCA(x,y);
        d[x]++,d[y]++,d[lca]-=2;
    }

    dfs2(1,-1);
    cout<<(ans==0?-1:ans)<<'\n';

    return 0;
}

/*
in:
6 2
1 2
2 3
4 3
2 5
6 5
3 6
4 5

out:
4
*/





【参考文献】
https://www.bilibili.com/video/BV1bg4y127QH
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:00  Triwa  阅读(0)  评论(0)    收藏  举报