NOIP2015提高组 运输计划 题解

题目链接:点这里
题目描述

公元 2044 年,人类进入了宇宙纪元。

L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球。

小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 u_i 号星球沿最快的宇航路径飞行到 v_i号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 t_j​,并且任意两艘飞船之间不会产生任何干扰。

为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。

在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。

如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?
输入格式

第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。

接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 a_i, b_i​ 和 t_i​,表示第 iii 条双向航道修建在 a_i​ 与 b_i​ 两个星球之间,任意飞船驶过它所花费的时间为 t_i​。

接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 u_j​ 和 v_j​,表示第 jjj 个运输计划是从 u_j​ 号星球飞往 v_j​号星球。
输出格式
输出格式
一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。

一句话题意
给出一棵n个节点的有边权树,设dis(i,j)表示i到j的距离,给出m个请求a_i,b_i,问如果将一条边权改为0,使max( dis(a_i,b_i) ) (1 <= i <= m)最小

一句话思路 二分答案+LCA倍增+树上差分

思路

首先看到使max最小第一个想到二分答案
设mid为所用的最短时间,如果ans为最小时间,那么ans+t时间之内也一定能完成,但ans-t却不能,因此满足单调性

接下来思考对于每个点对(a_i,b_i) (1 <= i <= m),如果要求他们的距离,O(n)的暴力肯定是不能接受的,所以考虑用LCA倍增优化

然后考虑怎么写check:对于dis大于mid的点对x,y,标记他们互相到达经过的边(设共计cnt个点对满足条件),最后找到标记次数为cnt次,长度最大的边,并用最大的dis 减去这条边长(一定要为cnt次是因为如果不是cnt次,那么一定会有一对点对之间距离不能减去这条边长,导致这个距离大于mid;而找长度最大的边自然是贪心思想:减去更大的长度更容易使最长的边长<=mid)
但是如果一个一个的标记的话时间复杂度就又变成O(n^2)的了,所以考虑树上差分:对于点对(u,v),分别将book[u],book[v] 分别加上 1,并将book[LCA(u,v)]减去2(这里和有些其他题解不同(TA们会把LCA(u,v) 和 fa[LCA(u,v)] 分别减去1),但是这里我标记的方法是对于一条边u -> fa[u] 标记u点,所以是将LCA(u,v) 减去2,而不是分别减1),然后重新跑一遍dfs作前缀和,复原每条边的真实标记,这样的话只需要O(n)就能跑完
最终时间复杂度
O(n * log(n * maxt) + n * log(n) )
(二分答案和树上差分) (预处理点对的LCA和距离)
上代码

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 3) + (x << 1) + ch - '0';ch = getchar();}
    return x * f;
}
int n, m;
const int maxn = 3e5 + 10,maxt = 1000;
int head[maxn], tot;
struct questions{
    int u, v, LCA, dist;
}ask[maxn];
struct edge{
    int topoint, nextedge, weight;
}e[maxn * 2];
void add(int u,int v,int w){
    e[++tot].nextedge = head[u];
    e[tot].topoint = v;
    e[tot].weight = w;
    head[u] = tot;
}
const int max2 = 20;
int dep[maxn], fa[maxn][max2 + 1], dis[maxn][max2 + 1];
void dfs(int u,int father,int depth,int dist){
    fa[u][0] = father; dep[u] = depth; dis[u][0] = dist;
    for(int i = head[u];i;i = e[i].nextedge){
        int v = e[i].topoint, w = e[i].weight;
        if(v == father)continue;
        dfs(v,u,depth + 1,w);
    }
}
void init(){
    for(int j = 1;j <= max2;j++){
        for(int i = 1;i <= n;i++){
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
            dis[i][j] = dis[i][j - 1] + dis[fa[i][j - 1]][j - 1];
        }
    }
}
questions getLCA(int u,int v){
    questions tmp;
    tmp.u = u; tmp.v = v;
    if(dep[u] < dep[v])swap(u,v);
    int dist = 0, father = 0;
    if(dep[u] != dep[v])
        for(int i = max2;i >= 0;i--){
            if(dep[fa[u][i]] >= dep[v]){
                dist += dis[u][i];
                u = fa[u][i];
            }
        }
    if(u == v){
        tmp.dist = dist;
        tmp.LCA = u;
        return tmp;
    }
    for(int i = max2;i >= 0;i--){
        if(fa[u][i] != fa[v][i]){
            dist += (dis[u][i] + dis[v][i]);
            u = fa[u][i]; v = fa[v][i];
        }
    }
    tmp.dist = dist + dis[u][0] + dis[v][0];tmp.LCA = fa[u][0];
    return tmp;
}
struct cmp{
    bool operator()(questions a,questions b){
        return a.dist > b.dist;
    }
};
int val[maxn];
void dfs1(int u,int father){
    for(int i = head[u];i;i = e[i].nextedge){
        int v = e[i].topoint;
        if(v == father)continue;
        dfs1(v,u);
        val[u] += val[v];
    }
}
bool check(int mid){
    int nn = 0;
    memset(val,0,sizeof(val));
    for(int i = 1;i <= m;i++){
        if(ask[i].dist <= mid)break;
        val[ask[i].u]++; val[ask[i].v]++;
        val[ask[i].LCA] -= 2; nn++;
    }
    if(!nn)return true;
    dfs1(1,0);
    int maxn = 0;
    for(int i = 1;i <= n;i++){
        if(val[i] == nn){
            maxn = max(maxn,dis[i][0]);
        }
    }
    return ask[1].dist - maxn <= mid;
}
signed main(){
    n = read(); m = read();
    int u, v, w;
    for(int i = 1;i < n;i++){
        u  =read(); v = read(); w = read();
        add(u,v,w); add(v,u,w);
    }
    dfs(1,0,0,0);
    init();
    for(int i = 1;i <= m;i++){
        u = read(); v = read();
        ask[i] = getLCA(u,v);
    }
    sort(ask + 1,ask + 1 + m,cmp());
    int l = 0, r = n * maxt, mid = 0;
    while(l < r){
        mid = (l + r) >> 1;
        if(!check(mid))l = mid + 1;
        else r = mid;
    }
    printf("%d\n",l);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值