「GXOI / GZOI2019」旅行者

传送门

这题给出两种方法,只给出前一种方法的代码,因为后一种代码可能跑不过(后面会讲)

方法1

我们不难发现最优情况下的路径,一定只在起点和终点两个位置是关键点,中间不可能有别的关键点,不然就可以只取一半,就会更优。

那么我们就可以建正反两张图,跑两次多源 \(\text{Dijkstra}\),记 \(f_u\) 为从关键点走到 \(u\) 的最短路,\(rf_u\) 记录是哪个点,\(g_u\) 表示从 \(u\) 点走到某一个关键点的最短路,\(rg_u\) 记录是哪个点。

那么我们枚举每一条边 \((u, v, w)\),如果 \(rf_u \ne rg_v\) ,就可以用 \(f_u + w + g_v\) 更新答案,如果 \(rf_u = rg_v\),可以证明这样一定不会是最优解。

方法2

新建超级源汇点 \(s, t\),把所有关键点分为 \(A, B\) 两个点集,钦定用一条路径 \(s \to a \to b \to t\)\(s \to b \to a \to t\) 更新答案,其中 \(a \in A, b \in B\)

考虑怎么分组以覆盖所有的组合情况,我们发现最优解中的 \(a, b\) 一定是两个不同的点,那么我们把这些点按照其二进制表示的某一位的值来分组,总共分 \(\log\) 次,不难发现这样一定可以覆盖所有可能的解。

分完组之后就直接把 \(s\)\(A\) 中的点连边权为 \(0\) 的边,\(B\) 中的点向 \(t\) 连边权为 \(0\) 的边,跑一边最短路就可以求出当前分组下 \(s \to a \to b \to t\) 类型的最优解,另一种类型同理。

这样复杂度是 \(n \log^2 n\) 的,理论上来说可以过,但是由于每次都要删边连边,所以操作不当就可能会 \(\text{TLE}\) ,这也是为什么不用这种方法的原因(((

方法1参考代码:

#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;

template < class T > void read(T& s) {
    s = 0; int f = 0; char c = getchar();
    while ('0' > c || c > '9') f |= c == '-', c = getchar();
    while ('0' <= c && c <= '9') s = s * 10 + c - 48, c = getchar();
    s = f ? -s : s;
}

typedef long long LL;
const int _ = 1e5 + 5, __ = 5e5 + 5;

int tot, head[_]; struct Edge { int v, w, nxt; } edge[__];
void Add_edge(int u, int v, int w) { edge[++tot] = (Edge) { v, w, head[u] }, head[u] = tot; }

int n, m, k, a[_], vis[_], r[2][_]; LL dis[2][_];
struct node { int u, v, w; } o[__];
priority_queue < pair < LL, int > > Q;

void Dijkstra(int x) {
    memset(dis[x], 0x3f, sizeof dis[x]);
    memset(r[x], 0, sizeof r[x]);
    memset(vis, 0, sizeof vis);
    for (int i = 1; i <= k; ++i)
        dis[x][a[i]] = 0, r[x][a[i]] = a[i], Q.push(make_pair(0, a[i]));
    while (!Q.empty()) {
        int u = Q.top().second; Q.pop();
        if (vis[u]) continue ; vis[u] = 1;
        for (int i = head[u]; i; i = edge[i].nxt) {
            int v = edge[i].v, w = edge[i].w;
            if (dis[x][v] > dis[x][u] + w)
                dis[x][v] = dis[x][u] + w, r[x][v] = r[x][u], Q.push(make_pair(-dis[x][v], v));
        }
    }
}

void solve() {
    read(n), read(m), read(k);
    for (int u, v, w, i = 1; i <= m; ++i)
        read(u), read(v), read(w), o[i] = (node) { u, v, w };
    for (int i = 1; i <= k; ++i) read(a[i]);
    memset(head, tot = 0, sizeof head);
    for (int i = 1; i <= m; ++i) Add_edge(o[i].u, o[i].v, o[i].w);
    Dijkstra(0);
    memset(head, tot = 0, sizeof head);
    for (int i = 1; i <= m; ++i) Add_edge(o[i].v, o[i].u, o[i].w);
    Dijkstra(1);
    LL ans = 1e18;
    for (int i = 1; i <= m; ++i) {
        int u = o[i].u, v = o[i].v, w = o[i].w;
        if (r[0][u] && r[1][v] && r[0][u] != r[1][v] && u != v)
            ans = min(ans, dis[0][u] + w + dis[1][v]);
    }
    printf("%lld\n", ans);
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("cpp.in", "r", stdin), freopen("cpp.out", "w", stdout);
#endif
    int T; read(T);
    while (T--) solve();
    return 0;
}
posted @ 2020-06-05 20:53  Sangber  阅读(241)  评论(2编辑  收藏  举报