• 洛谷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
    ", ans);
    
        return 0;
    }

     

  • 相关阅读:
    volatile关键字
    const关键字祥解
    extern关键字祥解
    gcc和g++使用澄清
    [APIO2014]连珠线
    点名
    四轮车
    盘子序列
    序列问题
    长途旅行
  • 原文地址:https://www.cnblogs.com/WAautomaton/p/11181217.html
Copyright © 2020-2023  润新知