P1948 USACO08JAN Telephone Lines S / AcWing 340. 通信线路 - 题解

https://www.luogu.com.cn/problem/P1948
https://www.acwing.com/problem/content/description/342/


方法一 分层图

每次选择从当前点前往另一点时要不要选择付费, 最多选择 \(k\) 次不付费, 所以就是 \(k + 1\) 层图.

具体来说, 假设 \(lev(u, i)\) 为第 \(i\) 层图的 \(u\) 点, 如果对于 \(u\) 如果有一条到 \(v\) 的边, 权值为 \(w\), 那就先在每层 \(lev(u, i) \leftrightarrow lev(v, i)\) 建权值为 \(w\) 的双向边, 再在 \(lev(u, i) \rightarrow lev(v, i + 1)\)\(lev(v, i) \rightarrow lev(u, i +1)\) 建权值为 \(0\) 的单向边, 表示做出了一次不付费的选择, 单向边防止再回来, 确保最多只能做 \(k\) 次不付费选择.

接着跑 \(dijkstra\), \(dist_i\) 表示从 \(1\)\(s\) 的所有路径中权值最大的边权值最小, 然后找出 \(dist_{lev(n, 0, 1, 2 \cdots k)}\) 中最小的那个即可.

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <array>
#include <queue>
using namespace std;
#define DLN(x) cout << #x << "\t = " << x << '\n';
#define CDLN(x, l, r) cout << #x << "\t = [ "; for (int i = l; i <= (int)r; i ++) cout << x[i] << ' '; cout << "]\n";
#define CCDLN(x, l, r, what) cout << #x << "\t = [ "; for (int i = l; i <= (int)r; i ++) cout << (what) << ' '; cout << "]\n";
template <typename T>
using vec = vector<T>;
const int inf = 0x3f3f3f3f;

struct Edge { int to, w; };

vec<int> dijkstra(vec<vec<Edge>>& e, int s) {
    using pii = pair<int, int>;
    priority_queue<pii, vec<pii>, greater<pii>> Q; // 权值最大的边权值最小, 所以优先找小的
    vec<bool> vis(e.size());
    vec<int> dist(e.size(), inf);
    Q.push({ 0, s });
    dist[s] = 0;

    while (!Q.empty()) {
        int k = Q.top().second, dis = Q.top().first; Q.pop();

        if (vis[k]) continue;
        vis[k] = true;

        for (Edge ie : e[k]) {
            int to = ie.to, w = ie.w;
            if (max(w, dist[k]) < dist[to]) {  // 如果 dist[to] 大于从 k 点到 to 的
                dist[to] = max(w, dist[k]);    // 更新
                Q.push({ w, to });
            }
        }
    }
    return dist;
}

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    vec<vec<Edge>> e((k + 1) * n + 1);

    #define lev(u, i) ((u) + (i) * n)

    for (int i = 1, u, w, v; i <= m; i ++) {
        cin >> u >> v >> w;
        for (int j = 0; j <= k; j ++) {
            e[lev(u, j)].push_back({ lev(v, j), w });
            e[lev(v, j)].push_back({ lev(u, j), w });
            if (j == k) continue;
            e[lev(u, j)].push_back({ lev(v, j + 1), 0 });
            e[lev(v, j)].push_back({ lev(u, j + 1), 0 });
        }
    }

    auto dist = dijkstra(e, 1);

    int minv = inf;
    // CDLN(dist, 0, dist.size() - 1);
    for (int j = 0; j <= k; j ++) {
        minv = min(dist[lev(n, j)], minv);
    }
    if (minv < inf) cout << minv;
    else puts("-1");

    return 0;
}

方法二 二分查找

如上文 "所有路径中权值最大的边权值最小", 像这种最大值最小或者最小值最大, 一般都是用二分来做的.

检查当前支出是否可以在不付费次数 \(\le k\) 时可行, 显然这种可行解是有单调性的, 可以二分.

至于如何检查, 我们可以跑一轮 \(dijkstra\), 为了维持支出 \(\le mid\), 需要每次当边权 \(> mid\) 时选择不付费, 可以看成从当前点经过该边到另一点花费了一次不付费次数, 也就是边权为 \(1\), 其他情况边权为 \(0\). (所以也可以用 0-1BFS 做)

最后统计 \(dist_n\) 是否小于 \(k\) 即可

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <array>
#include <queue>
using namespace std;
#define DLN(x) cout << #x << "\t = " << x << '\n';
#define CDLN(x, l, r) cout << #x << "\t = [ "; for (int i = l; i <= (int)r; i ++) cout << x[i] << ' '; cout << "]\n";
#define CCDLN(x, l, r, what) cout << #x << "\t = [ "; for (int i = l; i <= (int)r; i ++) cout << (what) << ' '; cout << "]\n";
template <typename T>
using vec = vector<T>;

struct Edge { int to, w; };

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    vec<vec<Edge>> e(n + 1);
    for (int i = 1, u, v, w; i <= m; i ++) {
        cin >> u >> v >> w;
        e[u].push_back({ v, w });
        e[v].push_back({ u, w });
    }

    // 当支出必然大于 mid 时, 返回 true
    auto check = [&](int mid) {
        // dist[i] 表示从源点到 i 最少要不付费几次
        // 为了维持支出小于等于 mid, 显然每次当边权大于 mid 时就选择不付费
        vec<int> dist(n + 1, 0x3f3f3f3f);
        vec<bool> vis(n + 1);
        using pii = pair<int, int>;
        priority_queue<pii, vec<pii>, greater<pii>> Q;
        Q.push({ 0, 1 });
        dist[1] = 0;

        while (!Q.empty()) {
            int k = Q.top().second; Q.pop();

            if (vis[k]) continue;
            vis[k] = true;

            for (Edge& ie : e[k]) {
                int w = ie.w, to = ie.to;
                if (dist[k] + (w > mid) < dist[to]) {
                    dist[to] = dist[k] + (w > mid); // 当 w > mid 时, 不付费次数 + 1
                    Q.push({ dist[to], to });
                }
            }
        }

        // 当到 n 最少要不付费 k 次以上时, 支出必然大于 mid
        return dist[n] > k;
    };

    int l = -1, r = 1e6 + 10;

    while (l + 1 != r) {
        int mid = l + r >> 1;
        if (check(mid)) l = mid;
        else r = mid;
    }

    if (r > 1e6) cout << -1;
    else cout << r;

    return 0;
}
posted @ 2025-08-25 15:30  Ybmzx  阅读(12)  评论(0)    收藏  举报