bzoj4326 & UOJ150【NOIP2015】运输计划 ( 树上差分 + lca )

bzoj4326 & UOJ150【NOIP2015】运输计划

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

数据范围
N,M<=300000

题解:
(旧题重做。
以为是像天天爱跑步那样的解法,朝错误的方向想了很久。
偶然由“使最大的最小”想到二分,得解。)

二分最长的时间。
由此转化为不合法的边(大了的)能否通过删一条边,使其合法。

首先如果能通过删一条边,使他们都合法,那么这些路径必然有交边,且这些路径中最大值-交边中最大的权值<=二分的上界。

如何判断交边?查分即可,对任意路径(u,lca,v),c[u]++,c[v]++,c[lca]-=2。
那么一个点的c值就是其和其父亲的连边被多少路径经过。
对于所有c[u]>=cnt(不合法的边数)
只需判断是否存在 u与其父的连边权值>=路径中最大值 - 二分的上界

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=300005;
const int P=19;
int n,m,head[N],to[2*N],nxt[2*N],w[2*N],num=0,anc[N][P+1],c[N],dep[N],dis[N],path[N],cnt=0,flag=0;
void build(int u,int v,int ww)
{
    num++;
    to[num]=v;
    nxt[num]=head[u];
    w[num]=ww;
    head[u]=num;
}
struct node
{
    int x,y,lca;
    node(){}
    node(int x,int y,int lca):x(x),y(y),lca(lca){}
}a[N];
void dfs(int u,int f)
{
    dep[u]=dep[f]+1;
    anc[u][0]=f;
    for(int i=1;i<P;i++)
    anc[u][i]=anc[anc[u][i-1]][i-1];
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==f) continue;
        dis[v]=dis[u]+w[i];
        path[v]=w[i];
        dfs(v,u);
    }
}
int getlca(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    int d=dep[u]-dep[v];
    for(int i=0;d;i++,d>>=1)
    if(d&1) u=anc[u][i];
    if(u==v) return u;
    for(int i=P-1;i>=0;i--)
    if(anc[u][i]!=anc[v][i]) 
    {u=anc[u][i]; v=anc[v][i];} 
    return anc[u][0];
}
int getans(int u,int f,int val,int mx)
{
    int sum=c[u];
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(v==f) continue;
        sum+=getans(v,u,val,mx);
    }
    if(sum>=val&&path[u]>=mx) flag=1;
    return sum;
}
bool check(int x)
{
    memset(c,0,sizeof(c)); cnt=0; flag=0; int mx=0;
    for(int i=1;i<=m;i++)
    if(dis[a[i].x]+dis[a[i].y]-2*dis[a[i].lca]>x) 
    {c[a[i].x]++; c[a[i].y]++; c[a[i].lca]-=2; cnt++; mx=max(mx,dis[a[i].x]+dis[a[i].y]-2*dis[a[i].lca]);} 
    if(cnt==0) return 1;

    int whatever=getans(1,1,cnt,mx-x);
    return flag;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++)
    {
        int u,v,ww;
        scanf("%d%d%d",&u,&v,&ww);
        build(u,v,ww);
        build(v,u,ww);  
    } 
    dfs(1,1);
    for(int i=1;i<=m;i++)
    {
        int x,y,lca;
        scanf("%d%d",&x,&y);
        lca=getlca(x,y);
        a[i]=node(x,y,lca);
    }

    int lf=0; int rg=300000000;
    while(lf+1<rg)
    {
        int mid=(lf+rg)>>1;
        if(check(mid)) rg=mid;
        else lf=mid;
    }
    if(check(lf))  printf("%d\n",lf);
    else printf("%d\n",rg); 
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值