zoukankan      html  css  js  c++  java
  • 洛谷P2680 运输计划

    题目背景

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

    题目描述

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

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

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

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

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

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

    输入输出格式

    输入格式:

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

    接下来 n1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi两个星球之间,任意飞船驶过它所花费的时间为 ti。数据保证 1ai,bin 且 0ti1000。

    接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj号星球。数据保证 1ui,vin

    输出格式:

    一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。

    输入输出样例

    输入样例#1: 复制
    6 3 
    1 2 3 
    1 6 4 
    3 1 7 
    4 3 6 
    3 5 5 
    3 6 
    2 5 
    4 5
    输出样例#1: 复制
    11

    说明

    所有测试数据的范围和特点如下表所示

    请注意常数因子带来的程序效率上的影响。

    /*
         这道题目好难的 不愧为紫题(不像松鼠的新家QAQ)
         这道题可以说是树上差分+LCA+二分的综合题
         这次LCA用的树剖求,于是有了3个dfs 233333
         看题目就知道这是一棵树 使用边差分 将边权赋给点
         预处理出最长的计划的长度 max(query[i].dis = dis[query[i].start] + dis[query[i].end] - dis[query[i].lca] * 2)
         考虑二分路径的长度 记录下所有大于mid长度的计划 (此时小于mid的一定合法)
         如果删去这些路径公共的最大边 这些路径的长度都小于mid 那么这个mid合法 
         其他的下面的注释写的很详细了 
    */
    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 300005;
    
    int fa[maxn],top[maxn],size[maxn],son[maxn],depth[maxn];//这些是树链剖分需要的数组QAQ 
    int head[maxn],edge[maxn],dis[maxn],num[maxn],n,m,cnt,kkk;
    //有必要说明各个数组的含义:head不多说、edge表示边的序号、dis表示点到根节点的距离、num是树上差分用的 
    
    struct node{
        int to,pre,v;
    }G[maxn*2];//这个存的树,前向星法 
    
    struct Node{
        int anc,dist,start,end;
    }plan[maxn];
    
    void add(int from,int to,int val){
        G[++cnt].to = to;
        G[cnt].v = val;
        G[cnt].pre = head[from];
        head[from] = cnt;
    }
    
    void dfs1(int x){
        size[x] = 1;
        for(int i = head[x];i;i = G[i].pre){
            int cur = G[i].to;
            if(cur == fa[x]) continue;
            depth[cur] = depth[x] + 1;
            fa[cur] = x;edge[cur] = i;//与普通的树剖不同,这里要将边权赋给点 
            dis[cur] = dis[x] + G[i].v;//还要预处理每个点的dis
            dfs1(cur);
            size[x] += size[cur];
            if(size[cur] > size[son[x]]) son[x] = cur;
        }
    }
    
    void dfs2(int x,int t){
        top[x] = t;
        if(son[x]) dfs2(son[x],t);
        for(int i = head[x];i;i = G[i].pre){
            int cur = G[i].to;
            if(cur != fa[x] && cur != son[x])
                 dfs2(cur,cur);
        }
    }
    
    int lca(int x,int y){
        while(top[x] != top[y]){
            if(depth[top[x]] < depth[top[y]]) swap(x,y);
            x = fa[top[x]];
        }
        if(depth[x] > depth[y]) swap(x,y);
        return x;
    }//求LCA 千万别写错了233333
    
    void dfs3(int x,int f){
        for(int i = head[x];i;i = G[i].pre){
            int cur = G[i].to;
            if(cur == f) continue;
            dfs3(cur,x);
            num[x] += num[cur];
        }
    }//树上差分的合并
    
    int can(int x){
        memset(num,0,sizeof(num));//记得每一次要清零
        int sum = 0,maxl = 0;//sum表示路径长度大于mid的计划数 maxl表示最长的路径
        for(int i = 1;i <= m;i++){
            if(plan[i].dist > x){
                sum++;
                num[plan[i].start]++;
                num[plan[i].end]++;
                num[plan[i].anc]-=2;//树上差分的边差分操作
                maxl = max(maxl,plan[i].dist);
            }
        }
        dfs3(1,0);//合并
        for(int i = 1;i <= n;i++){
            if(num[i] == sum && maxl - G[edge[i]].v <= x) return 1;
        }//这里很关键:只有存在一条边被经过最多的sum次并且它在被删去以后剩下的链的长度比现在的答案mid小,答案才合法
        //具体为什么好好想一想就知道啦
        return 0;
    }
    
    int main(){
        int x,y,z;
        //freopen("testdata.in","r",stdin);
        scanf("%d%d",&n,&m);
        for(int i = 1;i < n;i++){
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);add(y,x,z);//树要加双向边
        }
        dfs1(1);dfs2(1,1);
        for(int i = 1;i <= m;i++){
            scanf("%d%d",&plan[i].start,&plan[i].end);
            plan[i].anc = lca(plan[i].start,plan[i].end);//求LCA
            plan[i].dist = dis[plan[i].start] + dis[plan[i].end] - 2 * dis[plan[i].anc];//求两点的距离
            kkk = max(kkk,plan[i].dist);//找到每个计划当中最长的链
        }
        int l = 0,r = kkk,ans = 0;
        while(l <= r){
            int mid = (l+r) >> 1;
            if(can(mid)) ans = mid,r = mid-1;
            else l = mid + 1;
        }//二分,注意答案是往小的方向找的
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    2019.04.19 坦克大战
    2019.04.18 异常和模块
    2019.04.17 面向对象编程篇207
    fork操作时的copy-on-write策略
    Redis阻塞原因
    Redis持久化-fork操作
    Redis持久化-AOF重写
    Redis持久化-aof
    Redis持久化
    Shopify给左右两边布局的banner图加链接,链接失败
  • 原文地址:https://www.cnblogs.com/bryce02/p/9900111.html
Copyright © 2011-2022 走看看