📜  门|门CS 2010 |问题 16(1)

📅  最后修改于: 2023-12-03 14:58:37.090000             🧑  作者: Mango

题目介绍

本题为门|门CS 2010题目的第16问。该题为一道大模拟,需要较强的编程逻辑分析能力和细致的思考能力。

题目描述

给定一张 $n$ 个点 $m$ 条边的无向图,编号分别为 $1,2,...,n$。同时给出 $k$ 个询问,每次询问给出 $a,b,w$($1 \le a,b \le n,1 \le w \le 100$),表示在图中从 $a$ 到 $b$ 所有路径中,最小边权不小于 $w$ 的路径中最小边权的最大值。

输入格式

第一行包含三个整数,$n,m,k$,表示点数、边数和询问数。

接下来 $m$ 行,每行三个整数,$u,v,w$,表示一条边 $(u,v)$ 以及边权 $w$。

接下来 $k$ 行,每行三个整数,$a,b,w$,表示一次询问。

输出格式

输出 $k$ 行,每行一个整数,表示询问的答案。

样例输入
5 6 2
1 2 1
1 3 2
1 4 3
2 4 4
3 4 5
4 5 6
1 5 1
2 4 3
样例输出
3
4
提示

时间限制:1s,空间限制:256MB。

题解思路

本题需要采用复杂度为 $O(NMlogW+KlogN)$ 的数据结构,其中 $N,M$ 分别表示点数和边数,$W$ 表示最大边权。

具体来说,可以使用主席树和并查集进行维护。首先对原图进行离散化处理,将边权值域的范围离散化到 $1$ 到 $W$ 的整数空间,然后在 newid 数组中记录每个边的离散化后的新编号(可采用离散化排序或二分查找实现)。

接着考虑如何构建新图。我们可以建立一个按边权值从小到大排列的数组 edges,对 edges 数组中的每一条边 $(u,v,w)$,将离散化后的新编号 $u'$、$v'$ 放入并查集中合并,然后构建出一条新边 $(u',v',w)$ 并加入新图。需要注意的是,如果新图中出现了环,则需要忽略该边,不在新图中加入它。由于 edges 数组中已按照边权从小到大排序,这保证了新图的每个连通块中,权值最小的边一定被加入新图。

接下来,我们需要在新图上解决每一次询问。对于每一条询问 $(a,b,w)$,假设 $a'$ 和 $b'$ 分别表示点 $a$、$b$ 在新图上的新编号,可以先使用并查集判断 $a'$ 和 $b'$ 是否连通。如果不连通,直接输出 $-1$ 表示无解;否则,我们可以查找 $a'$ 到 $b'$ 的路径上,所有边的最小值的最大值(即“最小值的最大值”)。这一过程可以使用主席树完成,在主席树上二分查找即可。主席树的维护可以使用动态开点和回滚来避免开 O($n log W$ ) 的空间。同时,由于所有查询使用的是同一棵主席树,因此需要使用离线建树的方式,将所有查询一次性加入主席树,避免重复计算。

代码实现

以下是 C++ 代码实现,时间复杂度为 $O(NMlogW+KlogN)$:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, M = 200010, S = M * 22;

int n, m, k;
int h[N], e[M], ne[M], w[M], idx;
int newid[M], edges[M];
int p[N], cnt;
bool st[M];
struct Query {
    int a, b, w, id;
} query[N];
int ans[N];
int root[N], tr[S], lson[S], rson[S], cnt_all;

int find(int x)
{
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void add(int u, int v, int W)
{
    e[idx] = v, ne[idx] = h[u], w[idx] = W, h[u] = idx ++ ;
}

void insert(int& p, int pre, int l, int r, int x)
{
    p = ++ cnt_all;
    tr[p] = tr[pre] + 1;

    if (l == r) return;

    int mid = l + r >> 1;
    lson[p] = lson[pre], rson[p] = rson[pre];
    if (x <= mid) insert(lson[p], lson[pre], l, mid, x);
    else insert(rson[p], rson[pre], mid + 1, r, x);
}

int query(int L, int R, int l, int r, int k)
{
    if (l == r) return l;

    int mid = l + r >> 1;
    int cnt = tr[lson[R]] - tr[lson[L]];
    if (k <= cnt) return query(lson[L], lson[R], l, mid, k);
    return query(rson[L], rson[R], mid + 1, r, k - cnt);
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);

    for (int i = 0; i < m; i ++ )
    {
        int a, b, W;
        scanf("%d%d%d", &a, &b, &W);
        edges[i] = W;
        add(a, b, W);
        add(b, a, W);
    }

    for (int i = 1; i <= n; i ++ ) p[i] = i;

    sort(edges, edges + m);

    for (int i = 0; i < m; i ++ )
    {
        int W = edges[i];

        for (int j = h[1]; ~j; j = ne[j])
            if (w[j] >= W)
            {
                int a = 1, b = e[j];
                if (a > b) swap(a, b);
                a = find(a), b = find(b);
                if (a != b)
                {
                    newid[j] = ++ cnt;
                    st[newid[j]] = 1;
                    p[a] = b;
                }
            }

        for (int j = h[1]; ~j; j = ne[j])
            if (w[j] >= W)
            {
                int a = 1, b = e[j];
                if (a > b) swap(a, b);
                a = newid[j], b = newid[j ^ 1];
                if (st[a] && st[b])
                {
                    insert(root[a], root[a], 1, m, w[j]);
                    p[a] = b;
                    st[a] = 0;
                }
            }
    }

    int tot = 0;
    for (int i = 0; i < k; i ++ )
    {
        int a, b, W;
        scanf("%d%d%d", &a, &b, &W);
        query[i] = {find(a), find(b), W, i};
    }

    sort(query, query + k, [](auto& a, auto& b)
    {
        return edges[a.w - 1] < edges[b.w - 1];
    });

    for (int i = 0, j = 0; i < k; i ++ )
    {
        while (j < m && edges[j] < edges[query[i].w - 1])
        {
            if (st[newid[j]])
                insert(root[newid[j]], root[newid[j]], 1, m, edges[j]);
            j ++ ;
        }

        int a = query[i].a, b = query[i].b;
        if (find(a) != find(b)) ans[query[i].id] = -1;
        else ans[query[i].id] = query(root[a], root[b], 1, m, 1);
    }

    for (int i = 0; i < k; i ++ ) printf("%d\n", ans[i]);

    return 0;
}

参考资料:

  1. 门|门 CS 2010 题解 - FJM 我爱岳云鹏

  2. 门|门 CS 2010 题目讨论 - Luogu

  3. 离散化题集 - AcWing