BZOJ 4326: NOIP2015 运输计划【LCA】【二分】【差分】

6 篇文章 0 订阅
4 篇文章 0 订阅

Description

公元 2044 年,人类进入了宇宙纪元。L 国有 n 个星球,还有 n−1 条双向航道,每条航道建立在两个星球之间,这 n−1 条航道连通了 L 国的所有星球。小 P 掌管一家物流公司, 该公司有很多个运输计划,每个运输计划形如:有一艘物流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之间不会产生任何干扰。为了鼓励科技创新, L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后,这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的物流公司的阶段性工作就完成了。如果小 P 可以自由选择将哪一条航道改造成虫洞, 试求出小 P 的物流公司完成阶段性工作所需要的最短时间是多少?

题解

由于要求的是最大值最小,所以很自然就想到了二分,我们二分答案,就会发现有一些路径不满足,所以,我们一定会把虫洞设在这些路径的交集上,只要最大的路径减去这些路径交集的最大边满足条件,那么就验证成功,判断是否是属于交集只要用差分就可以了。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 300006
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
    char ch=nc();int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
struct data{
    int x,y,fa,sum;
    bool operator <(const data&b)const{return sum<b.sum;}
}a[maxn];
int n,T,tot,L,R,Max,ans,cnt,tag[maxn],lnk[maxn],dep[maxn],p[maxn][20],fa[maxn][20],son[maxn*2],nxt[maxn*2],w[maxn*2];
bool vis[maxn];
void add(int x,int y,int z){
    nxt[++tot]=lnk[x];son[tot]=y;w[tot]=z;lnk[x]=tot;
}
void dfs(int x){
    vis[x]=0;
    for(int j=lnk[x];j;j=nxt[j]) if(vis[son[j]]){
        dep[son[j]]=dep[x]+1;fa[son[j]][0]=x;p[son[j]][0]=w[j];
        dfs(son[j]);
    }
}
void make_p(){
    for(int j=1;j<=19;j++)
     for(int i=1;i<=n;i++){
        fa[i][j]=fa[fa[i][j-1]][j-1];
        p[i][j]=p[i][j-1]+p[fa[i][j-1]][j-1];
     }
}
void get(int x,int y,int &s1,int &s2){
    int sum=0;
    if(dep[x]<dep[y])swap(x,y);
    for(int j=19;j>=0;j--) if(dep[x]-(1<<j)>=dep[y])sum+=p[x][j],x=fa[x][j];
    for(int j=19;j>=0;j--) if(fa[x][j]!=fa[y][j])sum+=p[x][j]+p[y][j],x=fa[x][j],y=fa[y][j];
    if(x!=y)s1=fa[x][0],s2=sum+p[x][0]+p[y][0];
       else s1=x,s2=sum;
}
void dfs2(int x){
    vis[x]=0;
    for(int j=lnk[x];j;j=nxt[j])if(vis[son[j]]){
        dfs2(son[j]);
        if(tag[son[j]]==cnt)ans=max(ans,w[j]);
        tag[x]+=tag[son[j]];
    }
}
bool check(int x){
    memset(vis,1,sizeof(vis));memset(tag,0,sizeof(tag));
    int p=-1;
    for(int i=1;i<=T;i++)if(a[i].sum>x){
        p=i;break;
    }
    if(p==-1)return 1;
    for(int i=p;i<=T;i++)tag[a[i].x]++,tag[a[i].y]++,tag[a[i].fa]-=2;
    ans=0;cnt=T-p+1;
    dfs2(1);
    return Max-ans<=x;
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n=_read();T=_read();
    for(int i=1,x,y,z;i<n;i++)x=_read(),y=_read(),z=_read(),add(x,y,z),add(y,x,z);
    memset(vis,1,sizeof(vis));dfs(1);make_p();
    for(int i=1;i<=T;i++){
        a[i].x=_read();a[i].y=_read();
        get(a[i].x,a[i].y,a[i].fa,a[i].sum);
        if(a[i].sum>Max)Max=a[i].sum;
    }
    sort(a+1,a+1+T);
    L=0;R=Max;
    while(L<=R){
        int mid=(L+R)>>1;
        if(check(mid))R=mid-1;
                 else L=mid+1;
    }
    printf("%d\n",L);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值