题面
https://www.luogu.org/problem/P5304
题解
可能是“最短路”标签下最难的一道题了吧,还是要补说几句的。
对于任意一对点,他们的最短路有两种情况:
- 一条边直达
- 经过别的点中转
第一种情况直接特判掉。
对于第二种情况
跑多源$dijkstra$,正跑一次,再反跑一次。每次记录这个点的最短路是由哪个点过来的。
本来是这样想的,第二次跑时,记录每个点正跑跑到的最近的点和反跑跑到的最近点,但是如果两个相等(比如存在环),就假了。
为了让这个过程更严谨些,我们在松弛操作的过程中,检查$(u,v)$ $from[u][1]$是否等于$from[v][0]$,如果不等于,可以用其更新答案。
莫名想到后缀自动机求$[l,r]$最长相同的后缀时用$set$进行启发式合并(我好像已经忘了怎么求),感觉有异曲同工之妙啊。
// luogu-judger-enable-o2 #include<cstdio> #include<iostream> #include<cstring> #include<vector> #include<queue> #define ri register int #define N 100500 #define M 1000500 #define LL long long #define INF 1000000007LL using namespace std; struct node { int v; LL dis; bool operator < (const node &rhs) const { return dis>rhs.dis; } }; int n,m,k; vector<int> id[N]; int to[M],len[M],cc; int a[N]; priority_queue<node> pq; LL dis[N][2],ans; int fro[N][2]; bool imp[N],vis[N]; void add_egde(int a,int b,int c) { id[a].push_back(++cc); to[cc]=b; len[cc]=c; id[b].push_back(++cc); to[cc]=a; len[cc]=c; } void dij(int opt) { for (ri i=1;i<=n;i++) dis[i][opt]=INF*INF,vis[i]=0; while (!pq.empty()) pq.pop(); for (ri i=1;i<=k;i++) { dis[a[i]][opt]=0; fro[a[i]][opt]=a[i]; pq.push((node){a[i],dis[a[i]][opt]}); } while (!pq.empty()) { int x=pq.top().v; pq.pop(); if (vis[x]) continue; vis[x]=1; for (ri i=0,ls=id[x].size();i<ls;i++) { int e=id[x][i]; if (e%2!=opt) continue; if (dis[x][opt]+len[e]<dis[to[e]][opt]) { dis[to[e]][opt]=dis[x][opt]+len[e]; fro[to[e]][opt]=fro[x][opt]; pq.push((node){to[e],dis[to[e]][opt]}); } if (opt) { if (dis[x][opt]+len[e]+dis[to[e]][opt^1]<ans && fro[x][opt]!=fro[to[e]][opt^1]) ans=dis[x][opt]+len[e]+dis[to[e]][opt^1]; } } } } int main(){ int T,z,y,x; scanf("%d",&T); while (T--) { scanf("%d %d %d",&n,&m,&k); for (ri i=1;i<=n;i++) id[i].clear(); cc=-1; for (ri i=1;i<=m;i++) { scanf("%d %d %d",&z,&y,&x); if (z!=y) add_egde(z,y,x); } ans=INF*INF; memset(imp,0,sizeof(imp)); for (ri i=1;i<=k;i++) { scanf("%d",&a[i]); imp[a[i]]=1; for (ri j=0,ls=id[a[i]].size();j<ls;j++) { int e=id[a[i]][j]; if (imp[to[e]] && len[e]<ans) ans=len[e]; } } dij(0); dij(1); for (ri i=1;i<=n;i++) if (fro[i][0]!=fro[i][1] && dis[i][0]+dis[i][1]<ans) ans=dis[i][0]+dis[i][1]; printf("%lld\n",ans); } }