[二分+LCA+差分]【NOIP2015D2T3】运输计划 题解

传送门:
洛谷:https://daniu.luogu.org/problemnew/show/2680#sub
(链接给的是大牛分站的,主站上T了一个点,大牛分站A了,“高性能”,卡常数)
UOJ:http://uoj.ac/problem/150

题目分析

给出一棵 n 个节点且有边权的数和m个从 si ti 的运输计划,完成所有计划所花费的时间为运输计划中的最大时间。现在可以选中树上的一条边并把它的边权变为0。求边权变化后完成所有计划所需时间的最小值。

解题分析

最大中求最小,套路想到二分。二分答案ans,预先LCA求出每个任务所需时间,判断出有多少个运输计划超出了ans。

因为题目中要求只能选一条边,所以我们肯定要求这条边在所有超出ans的运输计划,而且这条边肯定尽可能地大。可以用差分来找出有没有这条边。先刷出所有超出ans的运输计划个数k和最大的差距tem,然后sum数组,对于每一个计划 sum[si] 加1, sum[ti] 加1, sum[LCA(si,ti)] 减2。如果一条边被所有超出ans的运输计划覆盖,那么有 sum[son[j]]=k ,判断这条边能否减少到ans一下,则 w[j]>=tem ,不过代码中为了好写把这些赋值在点上。

注意每个任务的LCA值建议存储起来节省空间。

复杂度:
时间: O(mlogn+n log ai)
空间: O(n)

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 300005
#define maxe 600005
using namespace std;
int n,m,tot,len,ans,s[maxn],t[maxn],dis[maxn],cst[maxn],son[maxe],nxt[maxe],w[maxe],lnk[maxn],dep[maxn],fa[maxn][20],sum[maxn],v[maxn],la[maxn];
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void readi(int &x){
    x=0; char ch=nc();
    while ('0'>ch||ch>'9') ch=nc();
    while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=nc();}
}
void _add(int x,int y,int z){
    son[++tot]=y; w[tot]=z; nxt[tot]=lnk[x]; lnk[x]=tot;
}
void _dfs(int x,int dad){
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=dad){
            dep[son[j]]=dep[x]+1; dis[son[j]]=dis[x]+w[j]; _dfs(son[j],x); fa[son[j]][0]=x; v[son[j]]=w[j];
        }
}
void _init(){
    freopen("transport.in","r",stdin);
    freopen("transport.out","w",stdout);
    readi(n); readi(m); dis[1]=dep[0]=0;
    for (int i=1,x,y,z;i<n;i++){
        readi(x); readi(y); readi(z);
        _add(x,y,z); _add(y,x,z);
    }
    dep[1]=1;_dfs(1,0); len=19;
    for (int j=1;j<=len;j++)
        for (int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
}
int _LCA(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    for (int j=len;j>=0&&dep[x]>dep[y];j--) if (dep[fa[x][j]]>=dep[y]) x=fa[x][j];
    if (x==y) return x;
    for (int j=len;j>=0;j--)
        if (fa[x][j]!=fa[y][j]) {x=fa[x][j]; y=fa[y][j];}
    return fa[x][0];
}
void dfsc(int x,int dad){
    for (int j=lnk[x];j;j=nxt[j])
        if (son[j]!=dad) {dfsc(son[j],x); sum[x]+=sum[son[j]];}
}
bool _check(int x){
    int tem=0,k=0; memset(sum,0,sizeof(sum));
    for (int i=1;i<=m;i++)
        if (cst[i]>x){
            sum[s[i]]++; sum[t[i]]++; sum[la[i]]-=2;
            k++; tem=max(tem,cst[i]-x);
        }
    if (tem==0) return 1;
    dfsc(1,0); for (int i=2;i<=n;i++) if (sum[i]==k&&v[i]>=tem) return 1;
    return 0;
}
void _solve(){
    int L=0,R=0,mid;
    for (int i=1;i<=m;i++){
        readi(s[i]); readi(t[i]); la[i]=_LCA(s[i],t[i]);
        cst[i]=dis[s[i]]+dis[t[i]]-(dis[la[i]]<<1); R=max(R,cst[i]);
    }
    while (L<=R){
        mid=(R-L)/2+L;
        if (_check(mid)) R=mid-1; else L=mid+1;
    }
    printf("%d",L);
}
int main()
{
    _init();
    _solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/try__jhf/article/details/78342583