【GXOI/GZOI2019】旅行者

题面

 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
",ans);
  }
}
原文地址:https://www.cnblogs.com/shxnb666/p/11278508.html