P2680 运输计划[二分+LCA+树上差分]

Posted darkvalkyrie

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2680 运输计划[二分+LCA+树上差分]相关的知识,希望对你有一定的参考价值。

题目描述

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

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

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

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

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

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

解析

这题还是算比较简单的,暴力也能打满分。暴力一点的,树剖一条条试着删m条路径中的最长路径上的边就完事了。

题意很简单:给定带权树,将树中某条边的权值替换为0,并使替换后的最长路径最短。

看到最小化最大值,首先想到二分答案。首先我们要做一些基本的事情,这种在树上给定一对对起点终点的题目,肯定每对起点终点对应的路径是唯一的,我们先求出这些路径,即求LCA。


现在考虑如何二分,现在我们要解决的问题肯定就是在替换哪一条边、能不能替换的问题上了。显然,如果要使得所有起点终点对应的路径长度小于二分出的值,那么我们只需考虑那些比二分出的值大的路径。我们来考察这些路径,发现由于我们只能替换一条边,那么这条要替换的边就一定得是所有比二分出的值大的路径的公共边,否则至少要替换两条边才能使得当前二分情况成立。

那么如何判断所有比二分出的值大的路径有公共边呢?这里是难点。不难想出解决办法,就是统计所有比二分出的值大的路径的经过的边,做路径覆盖,如果最后某一条边被覆盖的次数与比二分出的值大的路径条数相等的话,这条边就是公共边。为了加快计算速度,我们可以使用树上差分技巧。

又如何判断当前二分情况是否成立呢?首先,如果我们没有找到公共边,那么当前情况一定不成立,其至少需要替换两条边。如果我们找到了一条以上的公共边,贪心的思想,一定是替换这些公共边中最长的那一条使得最长路径最短,而如果我们替换掉这最长的一条后最长路径仍然不满足当前二分出来的值,就不合法。

分析到这里,我们发现我们需要以下信息:

  • 所有起点和终点对应的最长的路径长度
  • 所有起点和终点对应的路径长度
  • 每对起点和终点的LCA
  • 二分时,树上每条边被覆盖的次数

这题也没什么好注意的细节问题,二分写好了就没事。

参考代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<ctime>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#define N 300010
#define ri register int
using namespace std;
inline int read()

    int f=1,x=0;char c=getchar();
    while(c<'0'||c>'9')if(c=='-')f=-1;c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0';c=getchar();
    return x*f;

int n,m,f[31][N],t,dep[N],d[N],c[N];
struct node
    int u,v,lca,dis;
s[N];
struct rec
    int next,ver,edge;
g[N<<1];
int head[N],tot,up[N];
inline void add(int x,int y,int val)

    g[++tot].ver=y,g[tot].edge=val;
    g[tot].next=head[x],head[x]=tot;

inline void init()

    queue<int> q;
    q.push(1);dep[1]=1;
    while(q.size())
        int x=q.front();q.pop();
        for(ri i=head[x];i;i=g[i].next)
            int y=g[i].ver,z=g[i].edge;
            if(dep[y]) continue;
            dep[y]=dep[x]+1;
            d[y]=d[x]+z,up[y]=z;
            f[0][y]=x;
            for(ri j=1;j<t;++j)
                f[j][y]=f[j-1][f[j-1][y]];
            q.push(y);
        
    

inline int lca(int x,int y)

    if(dep[y]>dep[x]) swap(x,y);
    for(int j=t;j>=0;--j)
        if(dep[f[j][x]]>=dep[y]) x=f[j][x];
    if(x==y) return x;
    for(int j=t;j>=0;--j)
        if(f[j][x]!=f[j][y]) x=f[j][x],y=f[j][y];
    return f[0][x];

inline void dfs(int x,int fa)

    for(ri i=head[x];i;i=g[i].next)
        int y=g[i].ver;
        if(y==fa) continue;
        dfs(y,x);
        c[x]+=c[y];
    

inline bool check(int x)

    int maxx=0,maxn=0,cnt=0;
    memset(c,0,sizeof(c));
    for(ri i=1;i<=m;++i)
        if(s[i].dis>x)
            cnt++;
            c[s[i].u]++,c[s[i].v]++;
            c[s[i].lca]-=2;
            maxx=max(maxx,s[i].dis);
        
    if(!cnt) return 1; 
    dfs(1,-1);
    for(ri i=1;i<=n;++i)
        if(c[i]==cnt) maxn=max(maxn,up[i]);
    return maxx-maxn<=x;

int main()

    int maxx=0;
    n=read(),m=read();
    t=log2(n)+1;
    for(int i=1;i<n;++i)
        int u=read(),v=read(),val=read();
        add(u,v,val),add(v,u,val);
    
    init();
    for(ri i=1;i<=m;++i)
        int f=read(),t=read();
        s[i].u=f,s[i].v=t;
        s[i].lca=lca(f,t);
        s[i].dis=d[f]+d[t]-d[s[i].lca]*2;
        maxx=max(maxx,s[i].dis);
    
    int l=0,r=maxx;
    while(l<r)
        int mid=(l+r)>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    
    printf("%d\n",l);
    return 0;

以上是关于P2680 运输计划[二分+LCA+树上差分]的主要内容,如果未能解决你的问题,请参考以下文章

NOIp2015 运输计划 [LCA] [树上差分] [二分答案]

P2680 运输计划

BZOJ 4326 NOIP2015 运输计划 (二分+树上差分)

[NOIP2015提高组]运输计划

Noip2015 运输计划 树上差分 二分答案

BZOJ-4326运输计划 树链剖分 + 树上差分 + 二分