[UVA10816] Travel in Desert 题解

[UVA10816] Travel in Desert 题解

题目描述

沙漠中有 \(n\) 个绿洲(编号为 \(1\sim n\) )和 \(m\) 条连接绿洲的 双向道路。

每条道路都有一个长度 \(d\) 和一个温度值 \(r\) 。

给定起点绿洲编号 \(s\) 和终点绿洲编号 \(t\) ,求出一条 \(s\) 到 \(t\) 的路径,使得这条路径上经过的所有道路的 最高温度尽量小,如果有多条路径,选择总长度最短的那一条。

输出路径,长度,最高温度。

思路

这题有两种解法。

1. 二分答案

看到这有种 最大值最小 描述的题目基本上都可以使用 二分答案 求解。

观察题目的性质,发现如果二分最高温度的话,答案满足二分性:

即最高温度越高,能走的边越多,连通 \(S, T\) 的可能性越大。

由此可以考虑二分一个最高温度 \(mid\),剩下的就是判断只靠路径温度 \(\leq mid\) 的边是否能走到 \(T\),这个可以使用 \(\text{Dijkstra}\) 算法在 \(O(m\log n)\) 的时间内进行判断,最优答案可以迭代记录。

每次查询时间复杂度:\(O(m\log n\log \text{R} )\),其中 \(R\) 是温度最大的取值,这里取 \(10^9\)。

Show the Code
// Problem: Travel in Desert
// URL: https://www.luogu.com.cn/problem/UVA10816
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2022-12-25 23:38:59

// 省略头文件

struct qwq
{
    int x;
    int y1, y2;
};
vector<qwq> g[N];

int dist[N], pre[N];
bool st[N];
int ansd, ansr, ans[N], cnt;
int n, m, S, T;
bool check(int x)
{
    for (int i = 1; i <= n; i++)
        dist[i] = INF;
    memset(st, 0, sizeof st);
    memset(pre, 0, sizeof pre);
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, S});
    dist[S] = 0;
    while (heap.size())
    {
        auto t = heap.top().y;
        heap.pop();
        if (st[t])
            continue;
        st[t] = 1;
        for (auto i : g[t])
        {
            int j = i.x;
            int r = i.y1, d = i.y2;
            if (r > x) // 判断当前边是否能走
                continue;
            if (dist[j] > dist[t] + d)
            {
                dist[j] = dist[t] + d;
                pre[j] = t;
                heap.push({dist[j], j});
            }
        }
    }
    if (dist[T] == INF)
        return false;
    if (ansr > x || (ansr == x && dist[T] <= ansd))
    {
        ansd = dist[T];
        ansr = x;
        cnt = 1;
        int i = T;
        while (i != S)
        {
            ans[cnt++] = i;
            i = pre[i];
        }
        ans[cnt] = S;
    }

    return true;
}

void init()
{
    ansd = ansr = 1e9 + 7;
    cnt = 0;
    for (int i = 1; i <= n; i++)
        g[i].clear();
}

int main()
{
    while (~scanf("%d%d%d%d", &n, &m, &S, &T))
    {
        init();
        for (int i = 1; i <= m; i++)
        {
            int a, b;
            double c1, c2;
            scanf("%d%d%lf%lf", &a, &b, &c1, &c2);
            g[a].push_back({b, (int)10 * c1, (int)10 * c2}); // 边权x10省去浮点数二分
            g[b].push_back({a, (int)10 * c1, (int)10 * c2});
        }

        int l = 0, r = 1e9;
        while (l <= r)
        {
            int mid = (l + r) / 2;
            if (check(mid))
                r = mid - 1;
            else
                l = mid + 1;
        }

        for (int i = cnt; i >= 2; i--)
            printf("%d ", ans[i]);
        printf("%d\n", T); // UVA省略行末空格,输出算错Q^Q
        printf("%.1lf %.1lf\n", 1.0 * ansd / 10, 1.0 * ansr / 10);
    }
    return 0;
}

2. 贪心(瓶颈生成树)

下面内容部分参考OI Wiki

引入两个概念。

  1. 无向图 \(G\) 的瓶颈生成树,它的最大的边权值在 \(G\) 的所有生成树中最小。
  2. 无向图 \(G\) 中 \(x\) 到 \(y\) 的最小瓶颈路是这样的一类简单路径,满足这条路径上的最大的边权在所有 \(x\) 到 \(y\) 的简单路径中是最小的。

两个性质:

  1. 最小生成树是瓶颈生成树的充分不必要条件。 即最小生成树一定是瓶颈生成树,而瓶颈生成树不一定是最小生成树。

关于最小生成树一定是瓶颈生成树这一命题,可以运用反证法证明:
我们设最小生成树中的最大边权为 ,如果最小生成树不是瓶颈生成树的话,则瓶颈生成树的所有边权都小于 ,我们只需删去原最小生成树中的最长边,用瓶颈生成树中的一条边来连接删去边后形成的两棵树,得到的新生成树一定比原最小生成树的权值和还要小,这样就产生了矛盾。

  1. \(x\) 到 \(y\) 的最小瓶颈路上的最大边权 \(w_1\) 等于最小生成树上 \(x\) 到 \(y\) 路径上的最大边权 \(w_2\)

反证法:
假设 \(w_1 \neq w_2\),那么有两种情况:

  1. \(w_1 > w_2\)。此时出现了一条新的最小瓶颈路,也就是最小生成树上 \(x\) 到 \(y\) 的路径 \(w_2\),所以原最小瓶颈路并非最小瓶颈路,矛盾。
  2. \(w_1 < w_2\)。此时若在原最小生成树的基础上删掉拥有 \(w_2\) 的边,并连上最小瓶颈路中拥有 \(w_1\) 的边,边权和一定比原最小生成树更小,矛盾。
    综上,原命题得证。

答案求的是原图的最小瓶颈路,它一定在原图的最小生成树上,这里的最小生成树中的最小指的是温度,因此只需要求出原图的最小生成树即可。

这么说可能不太好理解,可以参考 \(\text{Kruskal}\) 算法的过程:

  1. 对所有的边从小到大排序,并从小到大枚举
  2. 如果当前边 \((a, b)\) 沟通的两个点 \(a, b\) 已经被沟通了,那么当前边一定不如之前的路径,因为之前的路径的 \(\max{r}\) 一定 \(\leq r_{(a, b)}\)(其中 \(r\) 指的是温度)。
  3. 如果当前边 \((a, b)\) 沟通的两个点 \(a, b\) 还没沟通了,那么直接连通这两个点,原因同上。
  4. 如果 \(S, T\) 被连通了,那么后面的边都没有贡献了,直接 break。

这个过程能保证最后得到的图(最小生成树)是温度上的最优解,最后一步就是跑最短路即可,这类问题被称作:最小(大)瓶颈路。

每次查询时间复杂度 \(O(m\log m + n\log n)\)

// Problem: Travel in Desert
// URL: https://www.luogu.com.cn/problem/UVA10816
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2022-12-26 08:04:13

int n, m, S, T;

struct qwq
{
    int a, b, c, d;
};
vector<qwq> g;
int kruskal(int n) // Kruskal过程
{
    int fa[N], cnt = 0, sum = 0, mx = -INF;
    function<int(int)> find;
    find = [&](int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); };
    for (int i = 1; i <= n; i++)
        fa[i] = i;
    sort(g.begin(), g.end(), [](qwq a, qwq b) { return a.c < b.c; });
    for (auto i : g)
    {
        int x = find(i.a), y = find(i.b);
        if (x != y)
            sum += i.c, cnt++, fa[x] = y;
        mx = max(mx, i.c);
        if (find(S) == find(T))
            break;
    }
    return mx;
}

int h[N], ne[M], e[M], w[M], idx;
void add(int a, int b, int c)
{
    e[++idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx;
}

int dist[N], pre[N];
bool st[N];
void dijkstra(int s)
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    memset(pre, 0, sizeof pre);
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, s});
    dist[s] = 0;
    while (heap.size())
    {
        auto t = heap.top().y;
        heap.pop();
        if (st[t])
            continue;
        st[t] = 1;
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                pre[j] = t;
                heap.push({dist[j], j});
            }
        }
    }
}

void print(int p)
{
    if (p == 0)
        return;
    print(pre[p]);
    printf("%d ", p);
}

int main()
{
    while (cin >> n >> m >> S >> T)
    {
        g.clear();
        for (int i = 1; i <= m; i++)
        {
            int a, b;
            double c, d;
            cin >> a >> b >> c >> d;
            c = (int)10 * c, d = (int)10 * d;
            g.push_back({a, b, (int)c, (int)d});
        }
        int maxr = kruskal(n);
        memset(h, -1, sizeof h);
        idx = 0;
        for (auto i : g)
            if (i.c <= maxr)
                add(i.a, i.b, i.d), add(i.b, i.a, i.d);

        dijkstra(S);

        print(pre[T]);
        printf("%d\n", T);
        printf("%.1lf %.1lf\n", 1.0 * dist[T] / 10, 1.0 * maxr / 10);
    }

    return 0;
}
posted @ 2022-12-26 10:02  MoyouSayuki  阅读(47)  评论(0)    收藏  举报
:name :name