2026天梯赛L3-2花砖物语题解

本题的质量相比起其它题简直是断崖式提升,赛后补知识点补得我头疼

赛后才知道是SUA出的题...那没事了

悲报:本人赛时想到了一个能过16分的神秘贪心解法,但是因为忘记初始化,找了半个小时都找不到bug,痛失16分

引入

讲解本题前,需要引入关于二分图的几个知识点

  • 二分图的最大匹配

    定义:在一个二分图 \(G=(U \cup V, E)\) 中,匹配(Matching)是指一组没有公共端点的边的集合。最大匹配就是包含边数最多的匹配

  • 点覆盖

    定义:点覆盖(Vertex Cover)是指一个顶点子集 C,使得图中每条边至少有一个端点属于 C。最小点覆盖就是顶点数最少的点覆盖。

    通俗理解:选择最少的顶点来"覆盖"所有边(每条边至少有一个端点被选中)。

  • König 定理

    二分图的最大匹配=最小点覆盖数

  • 二分图的最大独立集

    定义:最大独立集问题是指,在一张无向图中选择最多的顶点,满足两两之间互不相邻.

    性质:最大独立集是最小点覆盖的补集,即:最大独立集+最小点覆盖 = n

了解更多讲解与证明请查阅OIWiki

思路

从工厂拿取的操作能且只能进行n次,所以我们需要最小化从桌面拿取的次数

最终答案为 n+桌面拿取次数

在桌面上,所有相同种类的砖可以看作是同一个点,因为一次操作作用于所有同一种类上

  • 建图

    将同一种类看作是一个点,在同一工厂的两个种类建一条边

    容易看出,建出的图正好是一个二分图

工厂操作最优是拿取所有同一种类的转,即删去一个顶点与其连接的边,没有边的时候,工厂操作停止

而我们的目的是,最小化工厂操作后剩余的点数,也就是最大化工厂操作删去的点数

于是,工厂操作的目标是选取最多的顶点,且满足顶点之间两两不相连。即:选取最大独立子集

最后,留在桌上的点即为最小点覆盖

所以,答案为 n+最小点覆盖 ,也就是 n+最大匹配,匈牙利算法即可求解最大匹配

于是题目到此为止,恭喜恭喜!

但是!!!

你愉快地套上匈牙利算法的板子,你就会愉快的发现,被卡了!

因为匈牙利算法的时间复杂度是 \(O(VE)\) ,在本题的数据量下,是过不去的。

于是,请使用基于最大流的Hopcroft–Karp 算法,可以将复杂度降低到 \(O(V^{\frac{1}{2}}E)\)

但是当你补习完最大流,并且理解了Hopcroft–Karp 算法 是如何基于网络流寻找最大匹配,于是愉快地套上模板交题,你就会发现

卡常了!!!

这里我推荐你使用这个引入随机化和神秘常数优化的板子,就能愉快地通过这道题了,恭喜恭喜(瘫)

模板原文链接

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define int LL

struct HopcroftKarp
{
    int n, m;
    vector<array<int, 2>> ver;
    vector<int> l, r;

    HopcroftKarp(int n, int m) : n(n), m(m)
    {
        l.assign(n, -1);
        r.assign(m, -1);
    }
    void add(int x, int y)
    {
        x--, y--;
        ver.push_back({x, y});
    }
    int work()
    {
        vector<int> adj(ver.size());
        mt19937 rgen(chrono::steady_clock::now().time_since_epoch().count());
        shuffle(ver.begin(), ver.end(), rgen);
        vector<int> deg(n + 1);
        for (auto &[u, v] : ver)
            deg[u]++;
        for (int i = 1; i <= n; i++)
            deg[i] += deg[i - 1];
        for (auto &[u, v] : ver)
            adj[--deg[u]] = v;

        int ans = 0;
        vector<int> a, p, q(n);
        while (true)
        {
            a.assign(n, -1), p.assign(n, -1);
            int t = 0;
            for (int i = 0; i < n; i++)
            {
                if (l[i] == -1)
                {
                    q[t++] = a[i] = p[i] = i;
                }
            }
            bool match = false;
            for (int i = 0; i < t; i++)
            {
                int x = q[i];
                if (~l[a[x]])
                    continue;
                for (int j = deg[x]; j < deg[x + 1]; j++)
                {
                    int y = adj[j];
                    if (r[y] == -1)
                    {
                        while (~y)
                        {
                            r[y] = x;
                            swap(l[x], y);
                            x = p[x];
                        }
                        match = true;
                        ans++;
                        break;
                    }
                    if (p[r[y]] == -1)
                    {
                        q[t++] = y = r[y];
                        p[y] = x;
                        a[y] = a[x];
                    }
                }
            }
            if (!match)
                break;
        }
        return ans;
    }
    vector<array<int, 2>> answer()
    {
        vector<array<int, 2>> ans;
        for (int i = 0; i < n; i++)
        {
            if (~l[i])
            {
                ans.push_back({i + 1, l[i] + 1}); // 转回 1-index 输出
            }
        }
        return ans;
    }
};

void solve()
{
    int n;
    cin >> n;
    HopcroftKarp flow(n, n);
    for (int i = 1; i <= n; i++)
    {
        int u, v;
        cin >> u >> v;
        flow.add(u, v);
    }
    cout << n + flow.work() << endl;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
}
posted @ 2026-04-20 21:36  Miaotofu01  阅读(122)  评论(0)    收藏  举报