树上差分例3 NOIP2015 运输计划 luoguP2680 [ 二分+lca+树上差分 ]

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/violinlove/article/details/83246412

传送门:https://www.luogu.org/problemnew/show/P2680

题目主旨:给你一棵带权树,给出一些航道的起点和终点,你要经过它们,就要消耗一次最长的总边长代价。

                   现请你删掉一条边,使得经过的最树上路径最

首先,我们可以想到暴力删边做法,只不过很暴力...

然后,考虑正解,我们二分最短路径的长,那么会有一些不合法的路径长度大于二分值。

           我们把这些不合法的路径找出来。因为所有路径都不合法,所以,所有路径都必须删掉同一条边,如果他们没有相同边,那么一定不满足二分条件。这一条相同边应该找长度最大的相同边,使得答案最小显然

            因为我们要找到所有不合法的num条路径的相同边 -> 这条被经过num

            所以记录边被经过的次数 -> 树上差分!!!在getans的时候直接找到可行边中最大值

            验证:最长路径 - 最大长度相同边 ?  二分值

            注意:不可以用 总和  -  总和,因为

                      (可能由于某些较短路径使得总和满足条件,但最长路径并不满足条件,使得答案变小)

扫描二维码关注公众号,回复: 3700845 查看本文章

                        ->luogu25pts

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define LL long long
#define N 300006
#define MB 600006 
#define M 300006
using namespace std;

inline int wread(){
    char c(getchar ());int wans(0),flag(1);
    while (c<'0' || c>'9'){if (c=='-') flag=-1;c=getchar ();}
    while (c>='0' && c<='9'){wans=wans*10+c-'0';c=getchar ();}
    return wans*=flag;
} 

inline void OUT (int x){
    if (x>9)	OUT(x/10);
    putchar (x%10+'0');
}
int n,m;
int K,hed[N];
struct node {int v,w,nxt;}e[MB];
void ad (int u,int v,int w){
    e[++K]=(node){v,w,hed[u]};hed[u]=K;
}
int p[N][20];
int dep[N];
int sav[N];//每个点所代表的那条边的长度 
int dis[N];//与根节点的距离 

void dfs (int x,int fa,int wx){
    sav[x]=wx;
    p[x][0]=fa;
    for (int i(1);i<=19;++i)	p[x][i]=p[p[x][i-1]][i-1];
    for (int i(hed[x]);i!=-1;i=e[i].nxt){
        int v(e[i].v);
        if (v==fa)	continue;
        dep[v]=dep[x]+1;
        dis[v]=dis[x]+e[i].w;
        dfs (v,x,e[i].w);
    }
    return ;
}

int lca (int a,int b){
    if (dep[a]>dep[b])	swap (a,b);
    for (int i(19);i>=0;--i)
        if (dep[a]<=dep[p[b][i]]) b=p[b][i];
    if (a==b)	return a;
    for (int i(19);i>=0;--i){
        if (p[a][i] == p[b][i])	continue;
        a=p[a][i]; b=p[b][i];
    }
    return p[a][0];
}

//树上差分 
int ans[N],nd,Jian;

void getans (int x,int fa){
    for (int i(hed[x]);i!=-1;i=e[i].nxt){
        int v(e[i].v);
        if (v==fa)	continue;
        getans (v,x);
        ans[x]+=ans[v];
    }
//因为差分的每条航道都不满足最短时间,所以每条航道都需要减去一条边 
    if (ans[x]==nd)	Jian=max (Jian,sav[x]);
    return ;
}

struct node2{int u,v,w;}b[M];

bool e666 (node2 x,node2 y){
    return x.w<y.w;
}

bool jud (int mid){
    memset (ans,0,sizeof ans);
    Jian=0;
    LL sum(0);
    nd=0;
    for (int i(m);i>=1;--i){
        if (b[i].w<=mid)	break;
        ++nd;
        sum+=b[i].w;
        int u(b[i].u),v(b[i].v);
        int lca_uv(lca(u,v));
        ans[u]++;ans[v]++;ans[lca_uv]-=2;
    }
    if (nd)	getans(1,0);
    else return true;
    if ( b[m].w-Jian <= mid )	return true;
    return false;
}

int main (){
    memset (hed,-1,sizeof hed);
    n=wread();m=wread();
    for (int i(1);i<n;++i){
        int u(wread()),v(wread()),w(wread());
        ad (u,v,w);ad (v,u,w);
    }
    dep[1]=1;
    dis[1]=0;
    dfs (1,0,0);
    for (int i(1);i<=m;++i){
        int u(wread()),v(wread());
        int w(dis[u]+dis[v]-(dis[lca(u,v)]<<1));
        b[i]=(node2){u,v,w};
    }
    sort (b+1,b+m+1,e666);
    int l(0),r(b[m].w);
    int pr=0;
    while (l<=r){
        int mid(l+r>>1);
        if (jud(mid))	pr=mid,r=mid-1;
        else l=mid+1;
    }
    OUT(pr);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/violinlove/article/details/83246412