[NOIP2015]运输计划

题意

Here

思考

这题题意简要来讲就是:给定树上许多条链,求删掉一条边后,所有链的最大值最小是多少

首先,各个点对间的距离可以用lca求出,主要问题是怎样考虑这个删边,删除一条边后会有以下两种情况:

  1. 取得最大值的点对间经过该边
  2. 取得最大值的点对间不经过该边

这样就很难处理了,我们可以考虑二分答案,只考虑所有距离大于该值的点对,最后判断删边后所有大于该值的点对是否小于该值即可,删边当然是删掉所有大于该值的点对的公共边中的最大值啦。如何记录最大公共边?直接树上差分最后跑一边dfs就好~(不过最后吐槽一句:这题太卡常了,我现在luogu还有一个点被卡)

代码


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x * f;
}
const int M = 300030;
const int N = 300030;
struct node{
    int nxt, from, to, dis, LCA;
}edge[M << 1], E[M];
int head[N], num;
void build(int from, int to, int dis){
    edge[++num].nxt = head[from];
    edge[num].to = to;
    edge[num].dis = dis;
    head[from] = num;
}
bool cmp(node a, node b){ return a.dis < b.dis; }
int d[N], f[N][20], dis[N];
void dfs(int u, int fa){
    for(int i=head[u]; i; i=edge[i].nxt){
        int v = edge[i].to;
        if(v == fa) continue;
        d[v] = d[u] + 1; f[v][0] = u; dis[v] = dis[u] + edge[i].dis;
        dfs(v, u);
    }
}
int lca(int u, int v){
    if(d[u] < d[v]) swap(u, v);
    for(int i=18; i>=0; i--) if(d[f[u][i]] >= d[v]) u = f[u][i];
    if(u == v) return u;
    for(int i=18; i>=0; i--) if(f[u][i] != f[v][i]) u = f[u][i], v = f[v][i];
    return f[u][0];
}
int n, m, val[N];
void dfs2(int u, int fa){
    for(int i=head[u]; i; i=edge[i].nxt){
        int v = edge[i].to;
        if(v == fa) continue;
        dfs2(v, u);
        val[u] += val[v];
    }
}
bool check(int now){
    memset(val, 0, sizeof(val)); int js = 0;
    for(int i=1; i<=m; i++){
        if(E[i].dis > now) val[E[i].from] ++, val[E[i].to] ++, val[E[i].LCA] -= 2, js ++;
    }
    dfs2(1, 0);
    int nowdis = 0;
    for(int i=1; i<=n; i++){
        if(val[i] == js) nowdis = max(nowdis, dis[i] - dis[f[i][0]]);
    }
    for(int i=1; i<=m; i++){
        if(E[i].dis - nowdis <= now) continue;
        else return 0;
    }
    return 1;
}
int main(){
    n = read(); m = read();
    for(int i=1; i<=n-1; i++){
        int u = read(), v = read(), d = read();
        build(u, v, d); build(v, u, d);
    }
    d[1] = 1; dfs(1, 0);
    for(int j=1; j<=18; j++)
        for(int i=1; i<=n; i++) f[i][j] = f[f[i][j-1]][j-1];
    int MAX = 0;
    for(int i=1; i<=m; i++){
        E[i].from = read(), E[i].to = read();
        E[i].LCA = lca(E[i].from, E[i].to);
        E[i].dis = dis[E[i].from] + dis[E[i].to] - 2 * dis[E[i].LCA];
        MAX = max(MAX, E[i].dis);
    }
    int l = 0, r = MAX, ANS = 0;
    while(l <= r){
        int mid = (l + r) >> 1;
        if( check(mid) ){
            ANS = mid;
            r = mid - 1;
        }
        else l = mid + 1;
    }
    cout << ANS;
    return 0;
}


总结

对于这一题,二分答案的原因是我们并不确定也不能直接算出最终的答案(因为有不同的情况),通过二分答案我们可以确定一个界限,方便我们求解。

posted @ 2018-11-05 20:17  alecli  阅读(105)  评论(0编辑  收藏  举报