P1041 · OFFICIAL SOLUTION

P1041 我们最好的最短路 官方题解

Gioush OJ · P1041 我们最好的最短路

我们最好的最短路

【题意简述】

给出一张 nn 个点、mm 条边的连通无向图,满足 mn20m-n\leq 20。多次询问两点之间的最短路。

【Hint】

【提示】

任取一棵生成树,非树边只有常数条。把所有非树边端点作为关键点,从每个关键点运行一次 Dijkstra。

【数据点 1∼21\sim 21∼2】

使用 Floyd 预处理任意两点之间的最短路。时间复杂度为 O(n3+q)\mathcal{O}(n^3+q),空间复杂度为 O(n2)\mathcal{O}(n^2)

【数据点 3∼43\sim 43∼4】

图是一棵树。倍增预处理最近公共祖先,并记录根到每个结点的距离 dud_u。树上两点距离为

distT(u,v)=du+dv2dLCA(u,v).\operatorname{dist}_T(u,v)=d_u+d_v-2d_{\operatorname{LCA}(u,v)}.

【数据点 5∼65\sim 65∼6】

询问数较小时,可以对每次询问从起点运行 Dijkstra。时间复杂度为 O(qmlogn)\mathcal{O}(qm\log n)

【正解】

从原图中任取一棵生成树 TT。非树边数量为

m(n1)=mn+121.m-(n-1)=m-n+1\leq 21.

把所有非树边的两个端点放入集合 KK,则 K42\lvert K\rvert\leq 42

先在生成树上倍增预处理最近公共祖先,从而支持树上距离 distT(u,v)\operatorname{dist}_T(u,v)。再从每个关键点 kKk\in K 出发,在原图上运行一次 Dijkstra,记到结点 vv 的最短距离为 distk(v)\operatorname{dist}_k(v)

对于询问 (u,v)(u,v),答案为

min{distT(u,v),minkK(distk(u)+distk(v))}.\min\left\{ \operatorname{dist}_T(u,v), \min_{k\in K}\bigl(\operatorname{dist}_k(u)+\operatorname{dist}_k(v)\bigr) \right\}.

若一条最短路不经过非树边,那么它完全位于生成树中,只能是树上 uuvv 的唯一简单路径,第一项得到答案。

若一条最短路经过非树边,那么它必然经过某条非树边的端点 kKk\in K。沿最短路在 kk 处分开,有

distk(u)+distk(v)dist(u,v).\operatorname{dist}_k(u)+\operatorname{dist}_k(v) \leq \operatorname{dist}(u,v).

左侧本身对应一条从 uu 经过 kkvv 的合法路线,又不可能小于全局最短路,所以该关键点给出的候选恰好等于答案。

【复杂度分析】

总时间复杂度为

O(nlogn+Kmlogn+q(K+logn)),\mathcal{O}\bigl(n\log n+\lvert K\rvert m\log n+q(\lvert K\rvert+\log n)\bigr),

空间复杂度为 O(nlogn+Kn+m)\mathcal{O}(n\log n+\lvert K\rvert n+m)。边权与路径长度需要使用 long long

【参考代码】

#include <queue>#include <vector>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 100010#define MAXK 45using namespace std; int c,n,m,q,In1,In2,In3,Head[MAXN],TreeHead[MAXN],Total=0,TreeTotal=0;int SetFa[MAXN],Size[MAXN],Fa[MAXN][22],Dep[MAXN],Key[MAXK],cnt=0;long long Dis[MAXN],Dist[MAXK][MAXN];bool Mark[MAXN]; struct edge {    int Ed,Next;    long long Val;}Edge[MAXN<<1],Tree[MAXN<<1]; void Edge_Add(int St,int Ed,int Val){Edge[++Total]={Ed,Head[St],Val};Head[St]=Total;}void Tree_Add(int St,int Ed,int Val){Tree[++TreeTotal]={Ed,TreeHead[St],Val};TreeHead[St]=TreeTotal;}int Find(int x){return SetFa[x]==x?x:SetFa[x]=Find(SetFa[x]);}void Insert(int x){if (!Mark[x]) Mark[x]=1,Key[++cnt]=x;} void Build() {    queue <int> Q;    Dep[1]=1;Q.push(1);    while (Q.size()) {        int Now=Q.front();Q.pop();        for (int i=TreeHead[Now];i;i=Tree[i].Next) {            int To=Tree[i].Ed;            if (To==Fa[Now][0]) continue;            Fa[To][0]=Now;Dep[To]=Dep[Now]+1;            Dis[To]=Dis[Now]+Tree[i].Val;            for (int j=1;j<=21;j++) Fa[To][j]=Fa[Fa[To][j-1]][j-1];            Q.push(To);        }    }} int LCA(int x,int y) {    if (Dep[x]<Dep[y]) swap(x,y);    for (int i=21;i>=0;i--) if (Dep[Fa[x][i]]>=Dep[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];} long long Query(int x,int y) {    int lca=LCA(x,y);    return Dis[x]+Dis[y]-2*Dis[lca];} priority_queue<pair<long long,int>,vector<pair<long long,int>>,greater<pair<long long,int>>> Q; void Dijkstra(int x,int S) {    memset(Dist[x],0x3f,sizeof(Dist[x]));    Dist[x][S]=0;Q.emplace(0,S);    while (Q.size()) {        long long Value=Q.top().first;        int Now=Q.top().second;Q.pop();        if (Value!=Dist[x][Now]) continue;        for (int i=Head[Now];i;i=Edge[i].Next) {            int To=Edge[i].Ed;            if (Dist[x][To]>Dist[x][Now]+Edge[i].Val) {                Dist[x][To]=Dist[x][Now]+Edge[i].Val;                Q.emplace(Dist[x][To],To);            }        }    }} int main() {#ifndef ONLINE_JUDGE    freopen("shortest.in","r",stdin);    freopen("shortest.out","w",stdout);#endif    scanf("%d",&c);    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++) SetFa[i]=i,Size[i]=1;    for (int i=1;i<=m;i++) {        scanf("%d%d%d",&In1,&In2,&In3);        Edge_Add(In1,In2,In3);Edge_Add(In2,In1,In3);        int x=Find(In1),y=Find(In2);        if (x!=y) {            if (Size[x]>Size[y]) swap(x,y);            SetFa[x]=y;Size[y]+=Size[x];            Tree_Add(In1,In2,In3);Tree_Add(In2,In1,In3);        }        else Insert(In1),Insert(In2);    }    Build();    for (int i=1;i<=cnt;i++) Dijkstra(i,Key[i]);    scanf("%d",&q);    while (q-->0) {        scanf("%d%d",&In1,&In2);        long long Ans=Query(In1,In2);        for (int i=1;i<=cnt;i++) Ans=min(Ans,Dist[i][In1]+Dist[i][In2]);        printf("%lld\n",Ans);    }    return 0;}