洛谷 P2680 运输计划 树链剖分+LCA+树上差分+二分

洛谷 P2680 运输计划

题意
  • 给你一个 n n n 个点的树,对于 n − 1 n-1 n1 条边各有边权,
  • 给出一些点对 ( x , y ) (x,y) (x,y) ,同时定义 d i s ( x , y ) dis(x,y) dis(x,y) 表示 x , y x,y x,y 两点间的树上距离,
  • 现允许你将一条边的权值变为 0 0 0 ,请你最小化最大的 d i s ( x , y ) dis(x,y) dis(x,y) 的值。
解法

为了使最长路径最短,考虑二分答案。

那么如何判断 m i d mid mid 值是否可行呢。我们可以求出每一条路径的距离,如果这个距离大于 m i d mid mid ,就可以肯定删掉的那条边一定在这个路径上。那么可以发现,选择修改边权的那一条边一定是所有路径长度大于 m i d mid mid 的路径的交集。树上边差分来判断这条边(边的深度较大的结点)是否等于 c n t cnt cnt c n t cnt cnt 表示距离大于 m i d mid mid 的路径个数),如果等于 c n t cnt cnt m a x x ( 最 长 路 径 ) − v a l ( 这 条 边 的 权 值 ) ≤ m i d maxx(最长路径)-val(这条边的权值)\le mid maxx()val()mid ,则这个 m i d mid mid 是可行的。

求树上路径的距离,可以用树上前缀和处理,即 d i s ( u , v ) = d i s ( r , u ) + d i s ( r , v ) − 2 ∗ d i s ( r , l c a ( u , v ) ) dis(u,v)=dis(r,u)+dis(r,v)-2*dis(r,lca(u,v)) dis(u,v)=dis(r,u)+dis(r,v)2dis(r,lca(u,v)) l c a lca lca 可以用树链剖分来求,顺便在第一次dfs的时候维护出每一个结点到父亲的距离和dfs序。

判断某条边被访问的次数,用树上差分来处理。

代码
#pragma region
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
#pragma endregion
const int maxn = 3e5 + 5;
int n, m;
vector<pair<int, int>> g[maxn];
int fa[maxn], son[maxn], sz[maxn], dep[maxn];
int cnt, top[maxn];
int dnf[maxn], tot;  //dfs序
int dis[maxn], val[maxn];
void dfs1(int u, int f, int deep) {
    fa[u] = f, sz[u] = 1, dep[u] = deep;
    dnf[++tot] = u;
    for (auto e : g[u]) {
        int v = e.first, w = e.second;
        if (v == f) continue;
        val[v] = w;
        dis[v] = dis[u] + w;
        dfs1(v, u, deep + 1);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}
void dfs2(int u, int topf) {
    top[u] = topf;
    if (!son[u]) return;
    dfs2(son[u], topf);
    for (auto e : g[u]) {
        int v = e.first;
        if (v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}
int Lca(int u, int v) {
    while (top[u] != top[v]) {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}
struct node {
    int u, v, lca, d;
} A[maxn];
int c[maxn];
bool check(int mid) {
    memset(c, 0, sizeof(c));
    int cnt_ = 0, maxx = 0;
    rep(i, 1, m) {
        if (A[i].d > mid) {
            maxx = max(maxx, A[i].d);
            c[A[i].u]++, c[A[i].v]++;
            c[A[i].lca] -= 2;
            cnt_++;
        }
    }
    for (int i = n; i >= 1; --i) {
        c[fa[dnf[i]]] += c[dnf[i]];
        if (c[dnf[i]] == cnt_ && maxx - val[dnf[i]] <= mid) return 1;
    }
    return 0;
}
int main() {
    scanf("%d%d", &n, &m);
    rep(i, 1, n - 1) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }
    dfs1(1, 0, 1);
    dfs2(1, 1);
    rep(i, 1, m) {
        scanf("%d%d", &A[i].u, &A[i].v);
        A[i].lca = Lca(A[i].u, A[i].v);
        A[i].d = dis[A[i].u] + dis[A[i].v] - 2 * dis[A[i].lca];
    }
    int l = 0, r = 1e9;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid + 1;
    }
    printf("%d\n", r);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值