题解 P5304 【[GXOI/GZOI2019]旅行者】

s_r_f

2019-04-19 23:20:34

Solution

~~我也很好奇大家的复杂度为什么多了一个$log$。。。~~ 一句话题意:有向图$G$中$|V| = n$,$|E| = m$,给定一个特殊点点集$S,|S| = k(p_1,p_2, ...,p_k)$ 求$min(dis(p_i,p_j)) (i ≠ j)$. 首先有一个很暴力的$O(Tnlognlogk)$的方法~~这也就是出题人开$5000ms$的原因~~: $i ≠ j$ 当且仅当$i$和$j$至少有一位二进制位不同。 枚举不同的二进制位,每次把$k$个点分成$S_0,S_1$两个集合跑$Dijsktra$ 求$log_2k$次最短路并取$min$. 然而这题确实有$O(Tnlogn)$的做法。 首先,我们求出$f[i],g[i]$为$i$到某个关键点/某个关键点到$i$的最短距离。 写成式子就是 $f[i] = min(dis_{min}(i,p_j))$ $g[i] = min(dis_{min}(p_j,i))$ 注意$dis_{min}(i,p_j)$和$dis_{min}(p_j,i)$并不是同一个东西,因为**这是一张有向图。** 同时我们求出$From[i],To[i]$表示$f[i],g[i]$中 $dis_{min}(i,p_j)$和$dis_{min}(p_j,i)$对应的$p_j$ 即$f[i]$的最短路走到了拿里,$g[i]$的最短路是从哪里来的。 上述内容只要做两次$Dijsktra$,一次在$G$上,一次在$G$的反图上。 复杂度$O(nlogn)$ 考虑枚举一条边$(u,v,w),$ 如果$From[u] ≠ To[v],$那么就用$g[u] + f[v] + w$来更新答案。 再枚举点$u,$ 如果$From[u] ≠ To[u],$那么就用$g[u] + f[u]$来更新答案。 做完了。 这样做为什么是对的呢? 引用一下某神犇的解释: "不难证明,对于源点 $i$,由 $i$ 拓展的点 $j$ 以及与 $j$ 相邻且不由$i$ 拓展的点 $k$, 如果 $i$ 的最优路径从 $j$ 走到了 $k$,那么走到拓展 $k$ 的源点是最优的。因此这个做法是正确的。" 对于一条最短的路径$x -> y$,假设其路径上没有边$(u,v)$满足$From[u] ≠ x,To[v] ≠ y$ 很显然的一点就是$f[v] <= dis_{min}(v,y)$且$g[u] <= dis_{min}(x,u)$ 如果此时$From[v] ≠ To[u],$那么存在比$x->y$不劣的解被更新了,对$ans$没有影响。 如果$From[v]$和$To[u]$都$≠x,y$,那么如果它们相等, 令$z = From[v]$,说明$dis_{min}(z,v) + w + dis_{min}(u,x)$也可以更新$ans$, 且$dis_{min}(x,u) >= dis_{min}(z,u), dis_{min}(v,y) >= dis_{min}(v,z)$ 如果存在$>1$个这样的点$z$,那么一定有路径$z_1->z_2$比$x ->y$不劣且被更新到,对$ans$没有影响。 如果只存在一个这样的$z$,就回到了上面一种情况,要么$x->y$只有一条边(显然不可能) ,要么就一定有$(u,v)$满足$From[v] ≠ To[u].$ 再讨论$From[v] = To[u] = x/y$的情况。 由于路径长为正整数,所以如果$From[v] = To[u] = x/y$, 那么找到它在$x->y$中的前/后的任意一条边,一定有$From[v] ≠ To[u].$ 证毕。 那么代码实现也就很简单了。 ```cpp #include <bits/stdc++.h> #define LL long long using namespace std; inline LL read(){ LL x = 0,f = 1; char c = getchar(); while (c != EOF && !isdigit(c)) {if (c == '-') f = -1;c = getchar();} while (isdigit(c)) {x = x * 10 + c - '0';c = getchar();} return x * f; } inline void write(LL x){ if (x < 0) putchar('-'),x = -x; if (x > 9) write(x/10); putchar(x%10+'0'); } inline void writeln(LL x){ write(x),putchar('\n'); } const int N = 100005,M = 500005; int Fr[M<<2],To[M<<2],Ne[M<<2],Dis[M<<2],He1[N],He2[N],_k; inline void add(int *He,int x,int y,int z){ ++_k,Fr[_k] = x,To[_k] = y,Dis[_k] = z,Ne[_k] = He[x],He[x] = _k; } int T,n,m,k,p[N]; const LL INF = 1ll<<60; int f1[N],f2[N]; LL dis1[N],dis2[N]; struct Node{ int x; LL d; Node (int xx = 0,LL dd = 0){ x = xx,d = dd; } inline bool operator < (Node x) const{ return d > x.d; } }t; priority_queue<Node>Heap; void Dij_1(){ //dis1 : p[i] -> x int i; while (!Heap.empty()) Heap.pop(); for (i = 1; i <= n; ++i) dis1[i] = INF,f1[i] = -1; for (i = 1; i <= k; ++i) dis1[p[i]] = 0,f1[p[i]] = p[i],Heap.push(Node(p[i],0)); int p,x; while (!Heap.empty()){ t = Heap.top(); Heap.pop(); if (t.d == dis1[t.x]) for (p = He1[t.x]; p ; p = Ne[p]) if (dis1[To[p]] > dis1[t.x] + Dis[p]){ dis1[To[p]] = dis1[t.x] + Dis[p]; f1[To[p]] = f1[t.x]; Heap.push(Node(To[p],dis1[To[p]])); } } } void Dij_2(){ //dis2 : x -> p[i] int i; for (i = 1; i <= n; ++i) dis2[i] = INF,f2[i] = -1; for (i = 1; i <= k; ++i) dis2[p[i]] = 0,f2[p[i]] = p[i],Heap.push(Node(p[i],0)); int p,x; while (!Heap.empty()){ t = Heap.top(); Heap.pop(); if (t.d == dis2[t.x]) for (p = He2[t.x]; p ; p = Ne[p]) if (dis2[To[p]] > dis2[t.x] + Dis[p]){ dis2[To[p]] = dis2[t.x] + Dis[p]; f2[To[p]] = f2[t.x]; Heap.push(Node(To[p],dis2[To[p]])); } } } LL ans; int main(){ int i,u,v,w; T = read(); while (T--){ _k = 0; memset(He1,0,sizeof(He1)); memset(He2,0,sizeof(He2)); n = read(),m = read(),k = read(); while (m--){ u = read(),v = read(),w = read(); if (u^v) add(He1,u,v,w),add(He2,v,u,w); } for (i = 1; i <= k; ++i) p[i] = read(); Dij_1(); Dij_2(); ans = INF; for (i = 1; i <= n; ++i) if (f1[i] ^ f2[i]) ans = min(ans,dis1[i] + dis2[i]); for (i = 1; i <= _k; i += 2) if (f1[Fr[i]]^f2[To[i]]) ans = min(ans,dis1[Fr[i]] + dis2[To[i]] + Dis[i]); writeln(ans); } return 0; } ```