题目大意
给你一个有向图,由n个点,m条边组成。你有一种魔法,可以将某条边的方向反转。在你最多使出一次魔法的情况下,使得起点S到终点T的距离最短。
思路要点
套路题。
首先我们把将一条边反转的图像画出来。
我们先考虑上图,没进行反转情况下的图。起点 S = 1 S=1 S=1,终点 T = 5 T=5 T=5,那么我们先跑一次 d i j k s t r a dijkstra dijkstra,得出最短距离为 min ( p a t h ( 1 → 2 → 4 → 3 → 5 ) , p a t h ( 1 → 3 ) ) = p a t h ( 1 → 3 ) = 5 + 4 = 9 \min(path(1\rightarrow2\rightarrow4\rightarrow3\rightarrow5),path(1\rightarrow3))=path(1\rightarrow3)=5+4=9 min(path(1→2→4→3→5),path(1→3))=path(1→3)=5+4=9。
然后当反转 ( 5 , 2 ) (5,2) (5,2)这条边后,就变成了下图:
这时候我们发现最短路更新了, p a t h ( 1 → 2 → 5 ) = 3 + 1 = 4 path(1\rightarrow2\rightarrow5)=3+1=4 path(1→2→5)=3+1=4。
这样已经出现了规律,我们再来考虑一幅更清晰的图。
这幅图中,起点 S = 3 S=3 S=3,终点 T = 6 T=6 T=6,那么最短路径长度为 p a t h ( 3 → 1 → 2 → 4 → 5 → 6 ) = 5 path(3\rightarrow1\rightarrow2\rightarrow4\rightarrow5\rightarrow6)=5 path(3→1→2→4→5→6)=5。我们先保存这个最短距离 a n s = 5 ans=5 ans=5。接下来当我们反转 ( 1 , 5 ) (1,5) (1,5)这条边的方向是,最短路径长度就变成 p a t h ( 3 → 1 → 5 → 6 ) = 3 path(3\rightarrow1\rightarrow5\rightarrow6)=3 path(3→1→5→6)=3。
观察两幅图可以得出,当我们翻转 ( u , v ) (u,v) (u,v)这条边时,最短路 S → T S\rightarrow T S→T会有一种新的走法 S → v → u → T S\rightarrow v \rightarrow u \rightarrow T S→v→u→T。所以当我们反转边时,只要考虑点 S S S和点 T T T距离 ( u , v ) (u,v) (u,v)的距离。但由于有很多条边,很多的 ( u , v ) (u,v) (u,v),所以我们不能以 u u u和 v v v跑最短路,我们将 S S S正向建图跑最短路,设为 d i s S dis_S disS数组,将 T T T反向建图跑最短路,设为 d i s T dis_T disT数组,所以我们只要枚举每条边 ( u , v ) (u,v) (u,v),判断反转当前边后最短路是否更优,比较 d i s S [ v ] + w + d i s T [ u ] dis_S[v]+w+dis_T[u] disS[v]+w+disT[u]和 a n s ans ans即可,其中 w w w是边 ( u , v ) (u,v) (u,v)的权值。
算法流程
首先 O ( n l o g n ) O(nlogn) O(nlogn)跑 S S S为起点的最短路,然后再 O ( n l o g n ) O(nlogn) O(nlogn)跑 T T T为起点的反向的最短路。得出 d i s S dis_S disS和 d i s T dis_T disT数组后先保存不反转时的答案。然后枚举每一条边 ( u , v ) (u,v) (u,v)判断反转这条边后最短路是否更优即可。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,inf=0x3f3f3f3f;
struct node{
int id,dis;
bool operator < (const node & b)const
{
return dis>b.dis;
}
};
int n,m,S,T,x,y,cnt,ans,a[N],b[N],c[N],new_a[N],new_b[N],f[N],f1[N];
priority_queue<node> Q;//堆优化dijkstra
inline void add(int x,int y){a[++cnt]=y,b[cnt]=c[x],c[x]=cnt;}//邻接表建图
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++)cin>>x>>y,add(x,y),new_a[i]=y,new_b[i]=x;
cin>>S>>T;
memset(f,inf,sizeof(f));f[S]=0;
Q.push((node){S,0});
while(!Q.empty()){
node x=Q.top();Q.pop();
for(int i=c[x.id];i;i=b[i])
if(f[x.id]+1<f[a[i]])
f[a[i]]=f[x.id]+1,Q.push((node){a[i],f[a[i]]});
}
//以S为起点跑最短路
cnt=0;memset(c,0,sizeof(c));
for(int i=1;i<=m;i++)add(new_a[i],new_b[i]);
memset(f1,inf,sizeof(f1));f1[T]=0;
Q.push((node){T,0});
while(!Q.empty()){
node x=Q.top();Q.pop();
for(int i=c[x.id];i;i=b[i])
if(f1[x.id]+1<f1[a[i]])
f1[a[i]]=f1[x.id]+1,Q.push((node){a[i],f1[a[i]]});
}
//以T为起点跑反向的最短路
ans=f[T];//不反转时的答案
for(int i=1;i<=m;i++)ans=min(ans,f[new_a[i]]+1+f1[new_b[i]]);//枚举每一条边,判断答案是否更优
return cout<<ans<<endl,0;
}
做题总结
对于一些反转边的方向的题目,有些是这种套路——跑正反两次最短路。这种题目其实并不难想,画几个图手推一下就可以找到规律了。有些难的最短路题一般是建图优化和跑最短路优化。