P1024 · OFFICIAL SOLUTION

P1024 万山舆共渡 官方题解

Gioush OJ · P1024 万山舆共渡

万山舆共渡

【题目简述】

给定一棵带权树和 mm 条运输路径。可以选择一条边使其边权变为 00,求所有路径长度最大值的最小可能值。

【数据点 1∼81\sim81∼8】

【一条运输计划】

m=1m=1 时,直接找出这条路径,设长度为 LL、其中最大边权为 WmaxW_{\max}。显然清零最大边最优,答案为 LWmaxL-W_{\max}。当 n,mn,m 均不大时,枚举清零哪条边,再重新计算所有路径并取最大值。

【数据点 9∼149\sim149∼14】

【链上的判定】

二分答案 tt。原长度不超过 tt 的运输计划无需处理;其余所有超时路径都必须经过同一条被清零的道路。

【解法】

对所有超时区间做差分。设超时路径有 cc 条,若某条道路被覆盖了 cc 次,它便同时属于全部超时路径。所有这种道路中取权值最大的 ww;设原最大路径长为 LmaxL_{\max},当且仅当 LmaxwtL_{\max}-w\le t 时当前 tt 合法。

【正解】

【树上差分】

对每条超时路径 (u,v)(u,v),令 z=LCA(u,v)z=\operatorname{LCA}(u,v),作

{Diff(u)Diff(u)+1,Diff(v)Diff(v)+1,Diff(z)Diff(z)2.\begin{cases} \operatorname{Diff}(u)\leftarrow\operatorname{Diff}(u)+1,\\ \operatorname{Diff}(v)\leftarrow\operatorname{Diff}(v)+1,\\ \operatorname{Diff}(z)\leftarrow\operatorname{Diff}(z)-2. \end{cases}

【解法】

回溯时将儿子的差分和加到父亲。把道路 (u,Fa(u))(u,\operatorname{Fa}(u)) 看作结点 uu 派生的道路,则回溯后的 Diff(u)\operatorname{Diff}(u) 正是这条道路被多少条超时路径覆盖。

若存在道路满足 Diff(u)=c\operatorname{Diff}(u)=c 且边权 wLmaxtw\ge L_{\max}-t,清零它后所有超时路径都不超过 tt;反之,任何可行道路都必须同时满足这两个条件。

【复杂度分析】

LCA 预处理为 O(nlogn)\mathcal{O}(n\log n);一次判定为 O(n+m)\mathcal{O}(n+m),总复杂度为 O(nlogn+(n+m)logV)\mathcal{O}(n\log n+(n+m)\log V)

【参考代码】

#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 300010using namespace std;int Total=0,Head[MAXN],Depth[MAXN],Count[MAXN],Fa[MAXN][22],D[MAXN],Diff[MAXN];struct edge{    int Ed,Next,Val;}Edge[MAXN<<1];void Edge_Add(int St,int Ed,int Val){    Edge[++Total]={Ed,Head[St],Val};    Head[St]=Total;}struct Plan{    int u,v,Val,lca;}p[MAXN];bool cmp(Plan a,Plan b) {return a.Val<b.Val;}void DFS(int Now,int From){    for(int i=1;i<=21;i++){        Fa[Now][i]=Fa[Fa[Now][i-1]][i-1];    }    for(int i=Head[Now];i;i=Edge[i].Next){        int To=Edge[i].Ed;        if(To==From) continue;        D[To]=D[Now]+1;        Depth[To]=Depth[Now]+Edge[i].Val;        Fa[To][0]=Now;        DFS(To,Now);    }    return;}inline int LCA(int x,int y){    if(D[x]<D[y]) swap(x,y);    for(int i=21;i>=0;i--){        if(D[Fa[x][i]]>=D[y]) x=Fa[x][i];    }    if(x==y) return x;    for(int i=21;i>=0;i--){        if(Fa[x][i]!=Fa[y][i]){            x=Fa[x][i];y=Fa[y][i];        }    }    return Fa[x][0];}int n,m,In1,In2,In3;bool Tag=0;inline int Run(int Now,int From,int cnt,int t){    int Sum=0,tmp=0;    for(int i=Head[Now];i;i=Edge[i].Next){        int To=Edge[i].Ed;        if(To==From)continue;        tmp=Run(To,Now,cnt,t);        Sum+=tmp;        if(tmp==cnt&&Edge[i].Val>=t){Tag=1;}    }    return Sum+Diff[Now];}bool Judge(int t){    int cnt=0;    Tag=0;    memset(Diff,0,sizeof(Diff));    for(int i=1;i<=m;i++){        if(p[i].Val<=t) continue;        Diff[p[i].u]++;Diff[p[i].v]++;        Diff[p[i].lca]-=2;        cnt++;    }    Run(1,1,cnt,p[m].Val-t);    if(Tag) return 1;    else return 0;}int main(){    memset(Depth,0,sizeof(Depth));    memset(Head,0,sizeof(Head));    memset(D,0,sizeof(D));    scanf("%d%d",&n,&m);    for(int i=1;i<=n-1;i++){        scanf("%d%d%d",&In1,&In2,&In3);        Edge_Add(In1,In2,In3); Edge_Add(In2,In1,In3);    }    D[1]=1;    DFS(1,1);    for(int i=1;i<=m;i++){        scanf("%d%d",&p[i].u,&p[i].v);        p[i].lca=LCA(p[i].u,p[i].v);        p[i].Val=Depth[p[i].u]+Depth[p[i].v]-2*Depth[p[i].lca];    }    sort(p+1,p+m+1,cmp);    int l=0,r=p[m].Val;    while(l<r){        int Mid=(l+r)>>1;        if(Judge(Mid)) r=Mid;        else l=Mid+1;    }    printf("%d",l);    return 0;}