[noip2015]运输计划——倍增

题目背景

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

题目描述

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

小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物

流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰。

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

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

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

输入输出格式

输入格式:
输入文件名为 transport.in。

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

接下来 n-1 行描述航道的建设情况,其中第 i 行包含三个整数 ai, bi 和 ti,表示第

i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。

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

输出格式:
输出 共1行,包含1个整数,表示小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
说明
请注意常数因子带来的程序效率上的影响。

首先注意到每一条航线的最短路径即为两点各自到达LCA的距离之和,所以为了处理每一条航线的最初长度,即可以采用倍增的做法来处理,由于若干条的航班是同时开始的,所以题意就是求在把一条边的边权变为零了之后的最大航线路程的最小值,所以可以采用二分答案法来枚举

有关于在枚举了答案之后如何进行答案的判断,我们可以枚举每一条航线,然后寻找时间大于mid的航线,对于每一条边对该航线做出的贡献进行统计,如果没有一条边使得该航线的时间符合要求,则可以return 0;但是还要记录每一条边在航线中出现的次数,最后判断如有一条边出现的次数等于不符合要求的航线个数并且做出的贡献是符合要求的,就可以return 1了,否则还是return 0;

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
#define Mid ((l+r)/2)
using namespace std;
const int maxn=300000+10;
inline void read(int &x){
    int sum=0;
    char c=getchar();
    while(c<'0' || c>'9')c=getchar();
    while(c>='0' && c<='9'){
        sum=(sum<<1)+(sum<<3)+(c^'0');
        c=getchar();
    }
    x=sum;
}
int n,m,beg[maxn],len;
int maxdep,dep[maxn],lca[maxn][20],lcw[maxn][20];//maxdep为最大深度,lca为倍增数组,lcw为到第2^j祖先的路径的长度总和
int x[maxn],y[maxn],an[maxn],cost[maxn];//x,y为航线的两点,an为祖先,cost为原本的时间
int l,r;
bool vis[maxn];
struct egde{
    int to;
    int last;
    int w;
}E[maxn*2];
inline void add(int u,int v,int w){
    ++len;
    E[len].to=v;
    E[len].last=beg[u];
    E[len].w=w;
    beg[u]=len;
}
inline void dfs(int u,int deep){//dfs初始化dep和maxdep
    if(deep>maxdep)maxdep=deep;
    dep[u]=deep;
    vis[u]=1;
    for(register int i=beg[u];i;i=E[i].last){
        int v=E[i].to;
        if(vis[v])continue;
        lca[v][0]=u;
        lcw[v][0]=E[i].w;
        dfs(v,deep+1);
    }
}
inline void cal(){//处理倍增数组
    for(register int i=1;(1<<i)<=maxdep;++i)
        for(register int j=1;j<=n;++j)
            if(dep[j]-(1<<i)>=1)
                lca[j][i]=lca[lca[j][i-1]][i-1];
    for(register int i=1;(1<<i)<=maxdep;++i)
        for(register int j=1;j<=n;++j)
            if(dep[j]-(1<<i)>=1)
                lcw[j][i]=lcw[j][i-1]+lcw[lca[j][i-1]][i-1];
}
inline void work(int a,int b,int i){//倍增
    int sum=0,ans=0;
    if(dep[a]<dep[b])swap(a,b);
    while(dep[a]>dep[b]){
        int k=0;
        while(dep[lca[a][k+1]]>=dep[b])++k;
        sum+=lcw[a][k];a=lca[a][k];
    }
    while(a!=b){
        int k=0;
        while(lca[a][k+1]!=lca[b][k+1])++k;
        sum+=lcw[a][k];sum+=lcw[b][k];
        a=lca[a][k];b=lca[b][k];
    }
    an[i]=a;
    cost[i]=sum;
}
inline bool check(int mid){//判断答案
    int have[maxn]={0},cnt[maxn]={0},CNT=0;
    for(register int i=1;i<=m;++i)
        if(cost[i]>mid){
            ++CNT;
            int a=x[i],b=y[i],Min=0x7fffffff;
            for(register int j=a;j!=an[i];j=lca[j][0]){
                if(cost[i]-lcw[j][0]>have[j])
                    have[j]=cost[i]-lcw[j][0];
                if(have[j]<Min)
                    Min=have[j];
                ++cnt[j];
            }
            for(register int j=b;j!=an[i];j=lca[j][0]){
                if(cost[i]-lcw[j][0]>have[j])
                    have[j]=cost[i]-lcw[j][0];
                if(have[j]<Min)
                    Min=have[j];
                ++cnt[j];
            }
            if(Min>mid)return false;
        }
    for(register int i=1;i<=n;++i)
        if(have[i]<=mid && cnt[i]==CNT)
            return true;
    return false;
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("plan.in","r",stdin);
    freopen("plan.out","w",stdout);
#endif
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=n-1;++i){
        int u,v,w;
        read(u);read(v);read(w);
        add(u,v,w);add(v,u,w);
    }
    for(register int i=1;i<=m;++i)
        read(x[i]),read(y[i]);
    dfs(1,1);
    cal();
    for(register int i=1;i<=m;++i){
        work(x[i],y[i],i);
        if(cost[i]>r)
        r=cost[i];
    }
    while(l+1<r){//二分答案啦
        if(check(Mid))r=Mid;
        else l=Mid+1;
    }
    if(check(l))printf("%d\n",l);
    else printf("%d\n",r);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值