[NOIp2015] 运输计划

类型:LCA+树上差分+卡常

传送门:>Here<

题意:给出一棵树(带权),以及$M$条路径。现在可以把其中一条边权变为0,问最长的路径最短是多少?

解题思路

史无前例的卡常题(爆了22发OJ),持续95分超过2小时……

考虑暴力的做法,枚举每条边变不变并打擂,复杂度$O(nm log n)$

仔细分析发现,答案满足单调性 因此可以二分答案$ans$,然后$O(m)$验证。于是我们要做的就是验证给出的一个答案是否合法。如果所有路径中的最长路径$mx leq ans$则当前答案可行,并且如果两者差值$mx-ans$能够通过把一条边变为0来消去则答案也可行。显然,由于只能改一条边,则这条边一定是所有超出$ans$的路径的一条公共边。因此问题转化为寻找在那些超出$ans$的路径中,是否存在一条公共边,并且其权值大于等于$mx-ans$。后者很容易判定,关键在于前者。如何验证公共边?只需要验证每条边被经过的次数,如果等于路径数则为公共边。在树上求解每条边被经过次数,用树上差分即可

然后就是可怕的卡常了

想要优化常数,首先快读不能少,另外用链式前向星存图,尽量缩小二分答案的左右边界。这道题可以缩到多小?由于只能弄掉一条边,因此左边界是给出路径中的最长的路径减去最长边权,右边界是最长路径,当然如果你愿意,还可以记录dfs序然后逆序求解差分,不过没什么影响。除此之外还有最重要的一点,由于一共只有$M$条路径,于是我们可以把$M$条路径的长度以及$LCA$求好,避免在$judge$时反复求解——这是一个大到可怕的优化

Code

/*By DennyQi 2018.8.16*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
const int MAXN = 300010;
const int MAXM = 600010;
const int INF = 1061109567;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar();return x * w;
}
int N,M,x,y,z,L,R,Mid,Ans;
int first[MAXM],nxt[MAXM],to[MAXM],cost[MAXM],cnt,_mx;
int f[MAXN][30],dis[MAXN],dep[MAXN],a[MAXN],b[MAXN],cf[MAXN],cul[MAXN],lca[MAXN],dist[MAXN];
inline void add(int u, int v, int w){
    to[++cnt]=v, cost[cnt]=w, nxt[cnt]=first[u], first[u]=cnt;
}
void DFS_init(int u, int _f, int d){
    dep[u] = d, f[u][0] = _f;
    for(int i = 1; (1 << i) <= d; ++i){
        f[u][i] = f[f[u][i-1]][i-1];
    }
    int v;
    for(int i = first[u]; i; i = nxt[i]){
        if((v = to[i]) == _f) continue;
        dis[v] = dis[u] + cost[i];
        DFS_init(v, u, d+1);
    }
}
inline int LCA(int a, int b){
    if(dep[a] < dep[b]) swap(a, b);
    for(int i = 25; i >= 0; --i){
        if(dep[a] - (1<<i) >= dep[b]){
            a = f[a][i];
        }
    }
    if(a == b) return a;
    for(int i = 25; i >= 0; --i){
        if(f[a][i] == f[b][i]) continue;
        a = f[a][i], b = f[b][i];
    }
    return f[a][0];
}
void getCF(int u, int _f){
    int v;
    cul[u] = cf[u];
    for(int i = first[u]; i; i = nxt[i]){
        if((v = to[i]) == _f) continue;
        getCF(v, u);
        cul[u] += cul[v];
    }
}
bool DFS(int u, int _f, int k, int _cnt){
    int v;
    for(int i = first[u]; i; i = nxt[i]){
        if((v = to[i]) == _f) continue;
        if(cul[v] == _cnt && cost[i] >= k) return 1;
        if(DFS(v, u, k, _cnt)) return 1;
    }
    return 0;
}
inline bool judge(int k){
    int _cnt=0;
    if(_mx <= k) return 1;
    memset(cf,0,sizeof(cf));
    memset(cul,0,sizeof(cul));
    for(int i = 1; i <= M; ++i){
        if(dist[i] > k){
            ++_cnt;
            cf[a[i]]++, cf[b[i]]++;
            cf[lca[i]] -= 2;
        }
    }
    getCF(1, 0);
    return DFS(1, 0, _mx-k, _cnt);
}
int main(){
    N=r, M=r;
    int tmp = 0;
    for(int i = 1; i < N; ++i){
        x=r, y=r, z=r;
        add(x, y, z);
        add(y, x, z);
        tmp = Max(tmp, z);
    }
    DFS_init(1, 0, 1);
    for(int i = 1; i <= M; ++i){
        a[i]=r, b[i]=r;
        lca[i] = LCA(a[i], b[i]);
        dist[i] = dis[a[i]] + dis[b[i]] - 2 * dis[lca[i]];
        _mx = Max(_mx, dist[i]);
    }
    L = _mx - tmp, R = _mx + 1;
    while(L <= R){
        Mid = (L + R) / 2;
        if(judge(Mid)){
            R = Mid - 1;
            Ans = Mid;
        }
        else{
            L = Mid + 1;
        }
    }
    printf("%d", Ans);
    return 0;
}
原文地址:https://www.cnblogs.com/qixingzhi/p/9486747.html