【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 的物流公司完成阶段性工作所需要的最短时间是多少?

  • 【Sample Input】
    (第一行包括两个正整数 n,m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。
    接下来 n−1 行描述航道的建设情况,其中第 i 行包含三个整数 ai,bi 和 ti,表示第 i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。数据保证 1≤ai,bi≤n 且 0≤ti≤1000。
    接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个运输计划是从 uj 号星球飞往 vj号星球。数据保证 1≤ui,vi≤n)
    6 3
    1 2 3
    1 6 4
    3 1 7
    4 3 6
    3 5 5
    3 6
    2 5

  • 【Sample Output】
    (输出文件只包含一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。)
    11

  • 【样例解释】
    将第1条航道改造成虫洞:则三个计划耗时分别为:11,12,11,故需要花费的时间为12。
    将第2条航道改造成虫洞:则三个计划耗时分别为:7,15,11,故需要花费的时间为15。
    将第3 条航道改造成虫洞:则三个计划耗时分别为:4,8,11,故需要花费的时间为11。
    将第4条航道改造成虫洞:则三个计划耗时分别为:11,15,5,故需要花费的时间为15。
    将第5条航道改造成虫洞:则三个计划耗时分别为:11,10,6,故需要花费的时间为11。
    故将第3条或第5条航道改造成虫洞均可使得完成阶段性工作的耗时最短,需要花费的时间为11。

  • 【数据范围】

    【NOIP2015】运输计划 {二分答案+倍增+树上差分}_第1张图片


【题解】二分答案+倍增+树上差分
题目给的任务是:给定一棵树和一些点对,现可以将一条树边的权值变为0,使点对最大距离最小并求最小距离。
{最大距离最小→二分答案}
这一点显然
{处理点对距离问题→倍增}
首先dfs求出树上每个点到根节点的距离d。
对于点对(xi,yi),dis[i]=d[xi]+d[yi]-2*d[lca(xi,yi)]。
{二分检验答案→树上差分}
设当前二分出来可能答案为s,什么样的边权值需变成0?
<1>首先从点对来看,对于dis[i]>x的点对,显然我们必须删掉其路径上的一条边;
<2>从边来看,如果删掉一条边能使所有的点对距离都小于等于x,那么这条边的权值 w>=max(dis[i])-x;
<3>问题转化为如何统计一条边在dis[i]>x的点对路径上的出现次数。这里用到树上查分来做。对于点对(xi,yi),设数组p,如果dis[i]>x,将p[xi]++, p[yi]++, p[lca(xi,yi)]–。这样做完有什么用呢?我们发现,如果此时统计以rt为根的子树的p数组和,得到的就是rt和father[rt]所连边要求的出现次数了。
//可以这么想,如果这条边被经过的话,必然一端在子树内,一端在子树外。这样统计起来就能得到答案。详见程序。


#include 
#include 
#include 
#define N 300005
struct edge{ int to,s,nxt;}e[N<<1];
struct plan{ int u,v,lca,dis;}a[N];
int n,m,l,r,mid,ans,cnt,mx,num,q[N],fi[N],f[N][20],d[N],dis[N],p[N];
bool bo[N];
    inline int read()
    {
        int c=getchar(),t=0;
        for (;c<48 || 57do{
            t=(t<<3)+(t<<1)+c-48;
            c=getchar();
        }while(48<=c&&c<=57);
        return t;
    }
    void add(int u,int v,int w)
    {
        e[++cnt].to=v;e[cnt].s=w;
        e[cnt].nxt=fi[u];fi[u]=cnt;
    }
    void lca()
    {
        int h=1,t=1;
        memset(bo,false,sizeof(bo));
        for (bo[q[1]=1]=true;h<=t;++h)
            for (int i=fi[q[h]];i;i=e[i].nxt)
                if (!bo[e[i].to])
                {
                    f[e[i].to][0]=q[h];
                    d[e[i].to]=d[q[h]]+1;
                    bo[q[++t]=e[i].to]=true;
                    for (int j=0,k=q[h];f[k][j];k=f[k][j++])
                        f[e[i].to][j+1]=f[k][j];
                }
    }
    int findlca(int u,int v)
    {
        if (d[u]for (int j;d[u]>d[v];u=f[u][j-1])
            for (j=1;d[f[u][j]]>d[v];++j);
        for (int j;u!=v;u=f[u][j-1],v=f[v][j-1])
            for (j=1;f[u][j]!=f[v][j];++j);
        return u;
    }
    int dfs(int x,int fa)
    {
        for (int i=fi[x];i;i=e[i].nxt)
            if (e[i].to!=fa)
            {
                dis[e[i].to]=dis[x]+e[i].s;
                dfs(e[i].to,x);
            }
    }
    bool dfs1(int x,int fa,int s)
    {
        for (int i=fi[x];i;i=e[i].nxt)
            if (e[i].to!=fa)
            {
                if (dfs1(e[i].to,x,e[i].s)) return true;
                p[x]+=p[e[i].to];
            }
        if (s>=mx && p[x]>=num) return true;
        return false;
    }
    bool check(int x)
    {
        for (int i=1;i<=n;++i) p[i]=0;
        mx=num=0;
        for (int i=1;i<=m;++i)
            if (a[i].dis>x)
            {
                mx=std::max(mx,a[i].dis-x);
                ++p[a[i].u];++p[a[i].v];
                p[a[i].lca]-=2;++num;
            }
        if (!num) return true;
        return dfs1(1,0,0);
    }
int main()
{
    n=read();m=read();
    for (int i=1;iint u,v,w;
        u=read();v=read();w=read();
        add(u,v,w);add(v,u,w);
    }
    lca();
    for (int i=1;i<=m;++i)
    {
        a[i].u=read();a[i].v=read();
        a[i].lca=findlca(a[i].u,a[i].v);
    }
    dfs(1,0);
    for (int i=1;i<=m;++i)
        a[i].dis=dis[a[i].u]+dis[a[i].v]-(dis[a[i].lca]<<1);
    ans=-1;
    for (l=0,r=1e9;l<=r;)
    {
        mid=(l+r)>>1;
        if (check(mid)) r=mid-1,ans=mid;
        else l=mid+1;
    }
    printf("%d\n",ans);
    return 0;
}

你可能感兴趣的:(倍增,二分答案)