洛谷P2680 运输计划(倍增LCA + 树上差分 + 二分答案)

【题目链接】

【思路】:

根据题意可以明显看出,当所有任务都完成时的时间是最终的结果,也就是说本题要求,求出最小的最大值。

那这样的话就暗示了将答案二分,进行check。

【check方法】:

如果说当前答案为ans,每个任务设为p[i],所花费的时间是p[i].tim,所有任务p[i].tim的最大值为maxdis

那么则将符合条件p[i].tim>=ans的数量num求出来,这个数量也就是符合条件的路径的数量(一个任务在u,v之间有一个简单路径很容易理解),

然后找到一个所有路径中他们的公共边(公共边就是这个边在符合条件的p[i]中出现的次数==num的边)中最大的一个mmax,如果说

maxdis-mmax<=ans那么check返回1,使得上界变成mid-1,理由是如果最长的路减去一个此时最大的公共边比此时答案ans小,说明ans还可以

继续减小,即让最大值变得更小,反之则不能。

【寻找公共边方法】:

使用树上差分,对任务进行离线处理,定义cnt[x]是x到x的父亲gra[x][0]这条边经过的次数,在check函数中,符合p[i].tim>ans的就让cnt[p[i].u] ++, cnt[p[i].v]++, cnt[p[i].lca] -= 2

所有的任务都判断完成时,进行Dfs更新所有节点的cnt[],当cnt[x]==num && dis[x]-dis[gra[x][0]]>=mmax时更新mmax。进行完Dfs后判断maxdis-mmax与ans关系即可。

 

#include <bits/stdc++.h>
using namespace std;

const int maxn = 3e5 + 5;
const int maxm = maxn;
const int inf = 0x3f3f3f3f;
int n, m, mmax, num, ans, maxdis;
struct edge{
    int to, w, next;
} ed[maxn<<1];
struct plan{
    int u, v, lca, tim;
    plan( int u=0, int v=0, int lca=0, int tim=0 ): 
        u(u),v(v),lca(lca),tim(tim){}
} p[maxm];
int head[maxn], tot, cnt[maxn];
int gra[maxn][25], dep[maxn], dis[maxn], maxdep;
inline int read(){
    int k=0, f=1;
char ch=getchar();
    while( ch>'9'|| ch<'0' ){ if( ch=='-' ) f = -1; ch = getchar(); }
    while( ch<='9' && ch>='0' ){ k = k*10+ch-'0'; ch = getchar(); }
    return k*f;
}

inline void init(){
    memset( head ,-1 ,sizeof(head) );
    memset( gra, 0, sizeof(gra) );
    maxdep = log(n)/log(2);
    tot = 1;
}

inline void add( int u, int v, int w ){
    ed[++tot].to = v;
    ed[tot].w = w;
    ed[tot].next = head[u];
    head[u] = tot;
}

inline void dfs_lca( int x ){                       //初始化与LCA相关的数据
    for( int i=1; i<=maxdep; i++ ){
        gra[x][i] = gra[gra[x][i-1]][i-1];
        if( !gra[x][i] ) break;
    }
    for( int i=head[x]; ~i; i=ed[i].next ){
        int y = ed[i].to;
        if( y==gra[x][0] ) continue;
        dep[y] = dep[x]+1;
        dis[y] = dis[x]+ed[i].w;
        gra[y][0] = x;
        dfs_lca(y);
    }
}

inline void dfs_diff( int x ){
    for( int i=head[x]; ~i; i=ed[i].next ){
        int y = ed[i].to;
        if(y==gra[x][0]) continue;
        dfs_diff(y);
        cnt[x] += cnt[y];
    }
    if( cnt[x]==num && mmax<dis[x]-dis[gra[x][0]] )     //寻找最大的公共边
        mmax = dis[x]-dis[gra[x][0]];
}

inline void swap( int &a, int &b ){
    int t = a;
    a = b;
    b = t;
}

inline int LCA( int x, int y ){
    if(dep[x]>dep[y]) swap(x, y);
    for( int i=maxdep; ~i; i-- )
        if( gra[y][i]==x ) return x;                            //避免卡常,能return就return
        else if( dep[gra[y][i]]>=dep[x] ) y = gra[y][i];
    if( x==y ) return x;                                       //避免卡常
    for( int i=maxdep; ~i; i-- )
        if( gra[x][i]!=gra[y][i] ){
            x = gra[x][i];
            y = gra[y][i];
        }
    if( x!=y ) x = gra[x][0];
    return x;
}

inline int max( int a, int b ){
    return a>b ? a:b;
}

inline bool check( int x ){
    mmax = num = 0;                     //每次check都将mmax, num, cnt初始化为0
    memset( cnt ,0, sizeof(cnt) );
    for( int i=1; i<=m; i++ ){
        if( p[i].tim<=x ) continue;
        num ++;
        cnt[p[i].u] ++;
        cnt[p[i].v] ++;
        cnt[p[i].lca] -= 2;
    }
    dfs_diff(1);                //更新每个结点的cnt
    return maxdis-mmax<=x;
}

int main(){
    n = read(); m = read();                             //读取方式使用快读,此题卡常数卡的很厉害
    init();
    int maxe = -inf;
    for( int i=1; i<n; i++ ){
        int u, v, w;
        u = read(); v = read(); w = read();
        add(u, v, w);
        add(v, u, w);
        maxe = max( maxe, w );
    }
    dep[1] = 1;
    dis[1] = 0;
    dfs_lca(1);
    maxdis = -inf;
    for( int i=1; i<=m ;i++ ){
        int u, v;
        u = read(); v = read();
        int lca = LCA(u, v);
        p[i] = plan( u, v, lca, dis[u]+dis[v]-(dis[lca]<<1) );          //储存,后续进行离线处理
        maxdis = max( maxdis, p[i].tim );                               //获得一条边都不是虫洞的最大值
    }
    int l = maxdis-maxe, r = maxdis;            //这里要优化下界l,不然会超时
    while( l<=r ){
        int mid = (l+r)>>1;
        if( check(mid) ){
            ans = mid;
            r = mid-1;
        }
        else l = mid+1;
    }
    printf("%d\n", ans);

    return 0;
}

 

 

 

 

posted @ 2019-07-13 16:48  CoffeeCati  阅读(177)  评论(0编辑  收藏  举报