P2680 [NOIP2015 提高组] 二分 + 树上差分

题意

传送门 P2680 [NOIP2015 提高组] 运输计划

题解

考虑朴素的算法,枚举边权取零的边,统计路径权和,更新答案。总时间复杂度 O ( N 2 ) O(N^2) O(N2),显然难以胜任。

难以直接求解最短时间,那么考虑已知一个时间上界,如何验证是否存在一个边权取零的方案,使所需时间小于等于这个上界。设上界为 l i m lim lim,运输计划 i i i 所需时间为 D i D_i Di,对于 D i > l i m D_i>lim Di>lim 的情况,权值取零的边一定要属于运输计划 i i i 对应的路径。那么权值取零的边的可能集合,即所有满足 D i > l i m D_i>lim Di>lim 的路径的公共边,根据贪心策略,取其中权值最大的边可以使所需时间最短。

路径的公共边容易通过树上差分求解。具体而言,进行树上边差分,然后取前缀和,将运输路径 i i i 上,从 U i U_i Ui V i V_i Vi 的边记录值 c n t cnt cnt 增加一。那么任一树边的记录值,代表它被多少条运输路径所覆盖。显然,路径覆盖数等于满足 D i > l i m D_i>lim Di>lim 的路径数的边,属于决策集合。

倍增预处理 L C A LCA LCA,二分答案,总时间复杂度 O ( ( N + M ) log ⁡ N ) O\Big((N+M)\log N\Big) O((N+M)logN)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 300005, maxt = 1005, maxlg = 19;
int N, M, U[maxn], V[maxn], LCA[maxn], D[maxn], cnt[maxn];
int E, head[maxn], to[maxn * 2], cost[maxn * 2], nxt[maxn * 2];
int lg[maxn], dep[maxn], ds[maxn], par[maxn][maxlg];

void add_edge(int u, int v, int w) {
    
     to[E] = v, cost[E] = w, nxt[E] = head[u], head[u] = E++; }

void dfs(int u, int p, int d, int w)
{
    
    
    par[u][0] = p, dep[u] = d, ds[u] = w;
    for (int i = 1; i <= lg[dep[u]]; ++i)
        par[u][i] = par[par[u][i - 1]][i - 1];
    for (int i = head[u], v; ~i; i = nxt[i])
        if ((v = to[i]) != p)
            dfs(v, u, d + 1, w + cost[i]);
}

int lca(int u, int v)
{
    
    
    if (dep[u] < dep[v])
        swap(u, v);
    while (dep[u] > dep[v])
        u = par[u][lg[dep[u] - dep[v]]];
    if (u == v)
        return u;
    for (int k = lg[dep[u]]; k >= 0;)
        if (par[u][k] != par[v][k])
            u = par[u][k], v = par[v][k], k = lg[dep[u]];
        else
            --k;
    return par[u][0];
}

void get_sum(int u, int p, int num, int &res)
{
    
    
    int d = 0;
    for (int i = head[u]; ~i; i = nxt[i])
    {
    
    
        int v = to[i];
        if (v == p)
            d = cost[i];
        else
            get_sum(v, u, num, res), cnt[u] += cnt[v];
    }
    if (cnt[u] == num)
        res = max(res, d);
}

bool judge(int lim)
{
    
    
    int num = 0, mx_d = 0;
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < M; ++i)
        if (D[i] > lim)
        {
    
    
            ++num, mx_d = max(mx_d, D[i]);
            ++cnt[U[i]], ++cnt[V[i]], cnt[LCA[i]] -= 2;
        }
    int mx_t = 0;
    get_sum(0, -1, num, mx_t);
    return mx_d - mx_t <= lim;
}

int main()
{
    
    
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> N >> M;
    memset(head, -1, sizeof(head));
    for (int i = 1; i < N; ++i)
    {
    
    
        int a, b, t;
        cin >> a >> b >> t;
        --a, --b;
        add_edge(a, b, t), add_edge(b, a, t);
    }
    for (int i = 0; i < M; ++i)
        cin >> U[i] >> V[i], --U[i], --V[i];
    lg[0] = -1;
    for (int i = 1; i < N; ++i)
        lg[i] = lg[i >> 1] + 1;
    dfs(0, -1, 0, 0);
    for (int i = 0; i < M; ++i)
        LCA[i] = lca(U[i], V[i]), D[i] = ds[U[i]] + ds[V[i]] - ds[LCA[i]] * 2;
    int lb = -1, ub = N * maxt;
    while (ub - lb > 1)
    {
    
    
        int mid = (lb + ub) >> 1;
        if (judge(mid))
            ub = mid;
        else
            lb = mid;
    }
    cout << ub << '\n';
    return 0;
}

おすすめ

転載: blog.csdn.net/neweryyy/article/details/119342720