[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  阅读(16)  评论(0编辑  收藏  举报
:name :name