luogu2680 运输计划 题解

最小化完成所有任务的时间,考虑二分答案。

\(lca\) 算法预处理每个计划的距离,设第 \(i\) 个计划的距离为 \(W_i\)

如何判断完成任务的时间 \(t\) 是否可行呢?

不难想到,有以下两种情况。

  1. $ _{1 i m}{ { W_i }} t$。
  2. 将一条边权 为 \(dlt\) 的边改为 0(虫洞)后,$ _{1 i m}{ { W_i - dlt }} t$。

第一种情况很容易判断。

对于第二种情况,不难发现:将一条边权为 \(len\) 的边改为 0 后能使所有距离大于 \(t\) 的计划距离都变成不大于\(t\),当且仅当这些计划交于此边,且 $ len _{1 i m}{ { W_i - t }}$

这是显然的,证明略。

明确这个之后,问题仅仅在于,如何快速统计每条边被计划经过的次数和权。

边权不难求,用倍增 \(lca\)\(f\)\(dis\) 数组即可。设 \((x \rightarrow f(x,0))\) 边权为 \(d_x\),到根的距离为 \(dis_x\)

则 $ d_x=dis_x-dis(f(x,0)) $

考虑统计经过次数。

如果朴素地去统计,复杂度是 $ O(n)$ 的。可以用树上差分。

用一个计数数组 $ K$ 记录差分,计划 $ (x y)$,令 \[ K(x)+1,K(y+1),K(z)-2 \quad z=lca(x,y) \] 然而这个不像一般树上差分一样,统计子树信息。而且上述做法是边权差分,需要转化成点。

考虑每个计划路径是一条链,所以只需要统计每条链的信息就行了。

我们记录搜索树中每次访问到的点,即为 $ idf$。因为搜索树是一个 DAG,所以这个东西倒序就是逆拓扑序。

按照这个顺序令 $ K(f(idf(x),0))+K(idf(x))$ 就能自底向上统计出每个点的信息。

然后判断上述两种情况就行了。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=3e5+10;
int n, m, cnt, d[N], dis[N], f[N][18], k[N], idf[N];
int c, h[N], ver[N<<1], nxt[N<<1], w[N<<1];
struct E { int x, y, z, w; } g[N<<1];
void add(int x,int y,int z) { ver[++c]=y, w[c]=z, nxt[c]=h[x], h[x]=c; }
int r_() {
    int a=0; char c=getchar();
    for(;!isdigit(c);c=getchar());
    for(;isdigit(c);a=(a<<1)+(a<<3)+(c^48),c=getchar());
    return a;
}
void dfs(int x,int fr) {
    int i, y, z;
    idf[++cnt]=x, d[x]=d[fr]+1, f[x][0]=fr;
    for(i=1;(1<<i)<d[x];++i) f[x][i]=f[f[x][i-1]][i-1];
    for(i=h[x];i;i=nxt[i]) if(ver[i]!=fr) {
        y=ver[i], z=w[i];
        dis[y]=dis[x]+z, dfs(y,x);
    }
}
int lca(int x,int y) {
    int i, j;
    if(d[x]<d[y]) swap(x,y);
    for(i=0,j=d[x]-d[y];j;++i,j>>=1) if(j&1) x=f[x][i];
    if(x==y) return x;
    for(i=17;i>=0;--i) if(f[x][i]!=f[y][i]) x=f[x][i], y=f[y][i];
    return f[x][0];
}
bool C(int x) {
    int i, tot=0, ans=0;
    memset(k,0,sizeof(k));
    for(i=1;i<=m;++i) if(g[i].w>x) {
        ++k[g[i].x], ++k[g[i].y], k[g[i].z]-=2;
        ans=max(ans,g[i].w-x), ++tot;
    }
    if(!tot) return 1;
    for(i=n;i;--i) k[f[idf[i]][0]]+=k[idf[i]];
    for(i=2;i<=n;++i) if(k[i]==tot&&dis[i]-dis[f[i][0]]>=ans) return 1;
    return 0;
}
int main() {
    int i, x, y, z, l=0, r=0, mid;
    n=r_(), m=r_();
    for(i=1;i<n;++i) {
        x=r_(), y=r_(), z=r_();
        r+=z, add(x,y,z), add(y,x,z);
    }
    dfs(1,0);
    for(i=1;i<=m;++i) {
        x=r_(), y=r_(), z=lca(x,y);
        g[i].x=x, g[i].y=y, g[i].z=z;
        g[i].w=dis[x]+dis[y]-(dis[z]<<1);
    }
    while(l<r) {
        mid=l+r>>1;
        if(C(mid)) r=mid; else l=mid+1;
    }
    printf("%d\n",l);
}

luogu2680 运输计划 题解
https://yozora0908.github.io/2021/lg2680-solution/
作者
yozora0908
发布于
2021年10月4日
许可协议