首页 > 其他分享 >[题解] CF632F - Swimmers in the Pool

[题解] CF632F - Swimmers in the Pool

时间:2023-10-03 20:56:46浏览次数:374  
标签:return int 题解 dep GetLeader auto Swimmers CF632F leader

CF632F - Swimmers in the Pool

题目传送门

题意

给定一个大小为 \(n \times n\) 的矩阵 \(A\) 。假设 \(A\) 满足以下条件,那么称该矩阵为 MAGIC ,否则为 NOT MAGIC ,并输出对应的属性(即 \(A\) 是 MAGIC 还是 NOT MAGIC)。

  1. $ A_{i, j} = A_{j, i}$ ;
  2. $ A_{i,i} = 0 $ ;
  3. $ A_{i,j} \le \max { A_{i,k}, A_{j,k} } $。

思路

对于条件 \(1\) 和 \(2\) ,我们可以在 \(O(n^2)\) 的时间复杂度下直接进行判断即可。

对于条件 \(3\) ,我们在进行判断时需要对矩阵进行一下转换。

由条件 \(1\) ,我们可以知道这个矩阵是一个对称矩阵,那么我们可以想到图的一种表示方式:邻接矩阵。那么,在这里我们可以发现,整个矩阵描述的对象,是一个无向图。

由条件 \(2\) ,我们可以知道,该图没有自环(或者说自环的边权为 \(0\) ,可以忽略)。

因此,我们可以发现,我们是要在一张无向图上,判断 \((i, j)\) 这条边的边权是否小于等于 \((i, k)\) 和 \((k, j)\) 的边权。我们可以依据这个规律,不断扩展这个不等式:

\[A_{i, j} \le \max \{ A_{i, k}, A_{j, k} \} \\ A_{i, k} \le \max \{ A_{i, t}, A_{t, k} \} \\ \cdots \]

可以发现,如果我们不断扩展,那么该子问题可以转化为:问对于 \((i, j)\) 这条边,是否存在 \(i\) 到 \(j\) 的所有可能简单路径中,所有路径上边的边权都是大于等于 \((i, j)\) 的。

那么这样一来就很好解决了,我们要知道 \(i\) 到 \(j\) 所有路径上的情况,那么我们直接建一颗最小生成树就可以了,最小生成树上的路径边,即为原图中所有可能路径边的最小情况。

剩下的,我们只需要利用最小生成树,对所有 \(A_{i, j}\) 进行查询即可。查询的内容为:\(i\) 到 \(j\) 在最小生成树上的路径的边,其最最大的边是否大于等于 \(a_{i, j}\) 。

这个问题我们可以对最小生成树进行树剖+RMQ 查询,也可以使用 Kruskal 重构树查询。

实现

class DisjointSet {
private:
    std::vector<int> _leader;
    std::size_t _setCount;

public:
    explicit DisjointSet(std::size_t size)
        : _leader(size, -1), _setCount(size) {}

private:
    auto _InRange(int x) const noexcept -> bool {
        return 0 <= x && x < (int)_leader.size();
    }

    auto _GetLeader(int x) -> int {
        if (_leader[x] <= -1) {
            return x;
        }
        return _leader[x] = _GetLeader(_leader[x]);
    }

    auto _GetCount(int x) -> int {
        return -_leader[_GetLeader(x)];
    }

    template <std::strict_weak_order<int, int> _Compare>
    auto _MergeIf(int a, int b, const _Compare &comp) -> bool {
        a = _GetLeader(a);
        b = _GetLeader(b);
        if (!comp(a, b)) {
            std::swap(a, b);
        }
        if (!comp(a, b)) {
            return false;
        }
        _leader[a] += _leader[b];
        _leader[b] = a;
        --_setCount;
        return true;
    }

    auto _MergeTo(int a, int b) noexcept -> bool {
        a = _GetLeader(a);
        b = _GetLeader(b);
        if (a == b) {
            return false;
        }
        _leader[b] += _leader[a];
        _leader[a] = b;
        --_setCount;
        return true;
    }

public:
    auto GetLeader(int x) -> int {
        assert(_InRange(x));
        return _GetLeader(x);
    }

    auto GetCount() const noexcept -> std::size_t {
        return _setCount;
    }

    auto GetCount(int x) -> int {
        assert(_InRange(x));
        return _GetCount(x);
    }

    template <std::strict_weak_order<int, int> _Compare>
    auto MergeIf(int a, int b, const _Compare &comp) -> bool {
        assert(_InRange(a));
        assert(_InRange(b));
        return _MergeIf(a, b, comp);
    }

    auto MergeTo(int a, int b) -> bool {
        assert(_InRange(a));
        assert(_InRange(b));
        return _MergeTo(a, b);
    }

    auto IsSame(int a, int b) -> bool {
        assert(_InRange(a));
        assert(_InRange(b));
        return _GetLeader(a) == _GetLeader(b);
    }

    auto IsSame(std::initializer_list<int> list) -> bool {
        bool ret = true;
        int v = *list.begin();
        for (auto x : list) {
            ret = IsSame(v, x);
            if (!ret)
                break;
        }
        return ret;
    }

    template <typename _Iter>
        requires std::input_iterator<_Iter> &&
                 std::same_as<typename std::iterator_traits<_Iter>::value_type,
                              int>
    auto IsSame(_Iter first, _Iter last) {
        bool ret = true;
        int v = *first;
        for (auto it = first + 1; it != last && ret; ++it) {
            ret = IsSame(v, *it);
        }
        return ret;
    }

    auto Assign(std::size_t size) -> void {
        _leader.assign(size, -1);
        _setCount = size;
    }
};

auto Main() -> void {
    int n;
    cin >> n;

    vector mat(n, vector<int>(n));
    for (int i = 0; i < n; i += 1) {
        for (int j = 0; j < n; j += 1) {
            cin >> mat[i][j];
        }
    }

    for (int i = 0; i < n; i += 1) {
        if (mat[i][i] != 0) {
            cout << "NOT MAGIC\n";
            return;
        }
        for (int j = i + 1; j < n; j += 1) {
            if (mat[i][j] != mat[j][i]) {
                cout << "NOT MAGIC\n";
                return;
            }
        }
    }

    vector<tuple<int,int,int>> edge;
    edge.reserve(n * (n / 2 + 1));
    for (int i = 0; i < n; i += 1) {
        for (int j = i + 1; j < n; j += 1) {
            edge.emplace_back(mat[i][j], i, j);
        }
    }
    ranges::sort(edge, std::less{}, [](auto &&x) { return std::get<0>(x); });

    DisjointSet dsu(n * 2);
    int tot = n;
    vector<int> vw(n * 2);
    vector adj(n * 2, vector<int>{});
    for (auto [w, u, v] : edge) {
        int fu = dsu.GetLeader(u), fv = dsu.GetLeader(v);
        if (fu != fv) {
            vw[tot] = w;
            dsu.MergeTo(fu, tot);
            dsu.MergeTo(fv, tot);
            adj[tot].emplace_back(fu);
            adj[tot].emplace_back(fv);
            adj[fu].emplace_back(tot);
            adj[fv].emplace_back(tot);
            tot += 1;
        }
    } 

    vector<int> dep(n * 2, -1), siz(dep), top(dep), son(dep), fa(dep);
    auto build = [&](int tree_root) -> void {
        auto dfs1 = [&](auto &self, int from) -> void {
            son[from] = -1;
            siz[from] = 1;
            for (auto to : adj[from]) {
                if (dep[to] == -1) {
                    dep[to] = dep[from] + 1;
                    fa[to] = from;
                    self(self, to);
                    siz[from] += siz[to];
                    if (son[from] == -1 || siz[son[from]] < siz[to]) {
                        son[from] = to;
                    }
                }
            }
        };
        auto dfs2 = [&](auto &self, int from, int link_top) -> void {
            top[from] = link_top;
            if (son[from] == -1) {
                return;
            }
            self(self, son[from], link_top);
            for (auto to : adj[from]) {
                if (to != son[from] && to != fa[from]) {
                    self(self, to, to);
                }
            }
        };
        dep[tree_root] = 0;
        dfs1(dfs1, tree_root);
        dfs2(dfs2, tree_root, tree_root);
    };
    build(tot - 1);

    auto GetLca = [&](int a, int b) -> int {
        while (top[a] != top[b]) {
            if (dep[top[a]] < dep[top[b]]) {
                swap(a, b);
            }
            a = fa[top[a]];
        }
        if (dep[a] > dep[b]) {
            swap(a, b);
        }
        return a;
    };

    for (int i = 0; i < n; i += 1) {
        for (int j = i + 1; j < n; j += 1) {
            auto lca = GetLca(i, j);
            if (mat[i][j] > vw[lca]) {
                cout << "NOT MAGIC\n";
                return;
            }
        }
    }

    cout << "MAGIC\n";
}

标签:return,int,题解,dep,GetLeader,auto,Swimmers,CF632F,leader
From: https://www.cnblogs.com/FlandreScarlet/p/17741631.html

相关文章

  • GDCPC2023 B , D , F , K 题解
    和队友一起打的2023年广东省大学生程序设计竞赛重现赛,写了B,D,K,胡了一个F。D题目大意随着广东的建设与发展,越来越多人选择来到广东开始新生活。在一片新建的小区,有\(n\)个人要搬进\(m\)栋排成一行的房子,房子的编号从\(1\)到\(m\)(含两端)。房子\(u\)和\(v\)相邻......
  • 题解 [CSP-S 2021] 括号序列
    题目链接对于括号题,基本是栈匹配没有匹配的左括号和区间\(dp\)两个方向。这道题括号序列并不确定,只能用区间\(dp\)搞。如果直接设\(f_{l,r}\)表示\(l\simr\)的合法括号序列,那么由区间\(dp\)的套路可知,需要枚举中间点进行合并,那么\(()()()\)的情况就会出问题,原因是......
  • 【UVA 12100】Printer Queue 题解(队列+优先队列)
    计算机科学学生会中唯一的打印机正经历着极其繁重的工作量。有时,打印机队列中有100个作业,您可能需要等待数小时才能获得一页输出。由于某些作业比其他作业更重要,黑客将军为打印作业队列发明并实现了一个简单的优先级系统。现在,为每个作业分配1到9之间的优先级(9是最高优先级,1是最低......
  • [题解]CF1748C Zero-Sum Prefixes
    UPD23.10.3更新的对思路的描述,以及代码。思路对于每一个\(a_i=0\),如果我们将它变为\(x\),都可以直接将\(i\simn\)位置上的前缀和加\(x\)。设\(a_j\)是\(a_i\)后第一个\(0\),那么,在\(j\)时同样有上述规律。所以,我们只需在\(i\)时考虑,\(i\sim(j-1)\)的贡......
  • 题解 [蓝桥杯 2016 省 B] 交换瓶子
    题目链接本题解讲解环图的做法。要将一个\(1\simn\)的排列通过交换变成\(1\simn\),可以先将\(i\)向\(a_i\)连边,那么最终一定会练成若干个环(每个点只有一个出度,也只有一个入度)。假设交换在同一个环中的节点,一个环显然会变成两个环,也就是说,交换一次最多增加一个环的数量,......
  • 【题解】洛谷 P1003 [NOIP2011 提高组] 铺地毯
    原题链接解题思路如果直接按照题意开一个二维数组来模拟每个点最上面的地毯编号,会发现所占空间最坏情况下约为(2*105)2*4B=4*1010*4B=1.6*1011B≈149GB,程序完全无法运行。但实际上没有必要将每一个点的信息记录下来,只需要记录每一块地毯能覆盖哪些点,再依次判断哪那些地毯可以......
  • 「题解」Codeforces Round 894 (Div. 3)
    A.GiftCarpetProblem题目Sol&Code签到题#include<bits/stdc++.h>#defineN21typedeflonglongll;intmin(inta,intb){returna<b?a:b;}intmax(inta,intb){returna>b?a:b;}intT,n,m,a[4];std::strings[N];i......
  • T2回家(home)题解
    T2回家(home)现在啥也不是了,虽然会了逆元,但是对期望概率题还是一窍不通,赛时相当于只推出了\(n=1\)的情况,结果运用到所有情况,理所应当只有20分。题目描述小Z是个路痴。有一天小Z迷路了,此时小Z到家有NN个单位长度。小Z可以进行若干次行动,每次行动小Z有\(\frac12\)的概率向......
  • [ARC035B] アットコーダー王国のコンテスト事情 题解
    前置芝士排列组合分析明显的贪心,第一问与此题思路相似,优先选择做时间少的,可以尽可能让后面的罚时尽量的小。难点在第二问,第二问问的是有几种可能性,有个显然的结论:相同做题时间的题目,位置调换答案仍然相同。那么可以用桶+排列组合来解决:用桶储存这个做题时间的出现次数\(b......
  • AT_abc321_f 题解
    #思路简单动态规划,$dp_i$指当前操作后取和为$i$的球的方案数,每次输出$dp_K$即可。需要注意的是对于每次`+x`操作,计算$dp$数组时要倒着循环。时间复杂度:$O(QK)$。#代码```cpp#include<bits/stdc++.h>usingnamespacestd;longlongdp[5010];intmain(){ longlon......