AcWing 372. 棋盘覆盖


题目传送门

一、二分图应用【匈牙利算法求最大匹配】

前置知识

  • 匹配:二分图中两个点集中各取一点进行连边。

  • 最大匹配:二分图中左右两边匹配后边的最大数量

  • 匹配点:属于其中一个匹配边的其中一个端点。

  • 增广路径:左边点集中的一个非匹配点,沿着非匹配边走到右边点集中的一个匹配点,再沿着匹配边走到左边点集中的一个匹配点,再沿着非匹配边走到右边点集中的一个匹配点 …… 最终走到右边点集中的一个非匹配点

增广路径的起点和终点一定都是非匹配点,增广路径意味着我们可以将所有蓝色(匹配边)和绿色边(非匹配边)互换,这样匹配边的数量就加 \(1\)

结论
一个最大匹配 ? 不存在增广路径

替无可替,现在最大!

二、题目重述

给定一个 \(N\)\(N\) 列的棋盘,已知某些格子禁止放置。

求最多能往棋盘上放多少块长度为 \(2\) 、宽度为 \(1\) 的骨牌,骨牌的边界与格线重合(骨牌占用 \(2\) 个格子),并且任意两张骨牌都不重叠。

题目分析
将格子视为,所有相邻格子连一条边,然后问题即转化为最多选多少条匹配边,其中所有选出的边没有公共点。即求一个最大匹配。

一个 \(n×m\) 的矩形一定是可以二染色的。

如上图,可以将白色格子和黑色格子分别放在一边,相邻格子之间连一条边,可见边只会存在两个集合之间而不会出现在任一集合内部,故这是一个二分图,因此本题可以用二分图最大匹配做。

如何判断一个点是白色还是黑色?

可以找到一个规律,黑色格子点的横纵下标之和为偶数,白色格子点的横纵下标之和为奇数,称黑色格子为偶点,白色格子为奇点。在做最大匹配时,只需要枚举其中一个点集即可。

三、实现代码

#include 
#define x first
#define y second
using namespace std;
typedef pair PII;
const int N = 110;

int n; // n*n的矩阵
int t; // t为禁止放置的格子的数量
// 判断一个格子是否是坏点
bool g[N][N], st[N][N];
PII match[N][N];
int dx[] = {-1, 0, 1, 0}; //上右下左
int dy[] = {0, 1, 0, -1}; //上右下左

//匈牙利算法求二分图最大匹配 奇数点白,偶数点黑
bool find(int x, int y) {
    for (int i = 0; i < 4; i++) { //这里就看出使用邻接矩阵的优势了,要不怎么上下左右?
        int tx = x + dx[i], ty = y + dy[i];
        if (tx < 1 || tx > n || ty < 1 || ty > n) continue;
        if (st[tx][ty] || g[tx][ty]) continue;

        st[tx][ty] = true;
        PII t = match[tx][ty]; //白点一伙,黑点一伙,求白向黑的最大匹配边数量
        if (t.x == 0 || find(t.x, t.y)) {
            match[tx][ty] = {x, y};
            return true;
        }
    }
    return false;
}

int main() {
    cin >> n >> t;
    // t为禁止放置的格子的数量
    for (int i = 0; i < t; i++) {
        int a, b;
        cin >> a >> b;
        g[a][b] = true;
    }

    int res = 0;
    // 这里枚举奇数点
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            if ((i + j) % 2 && !g[i][j]) {
                //利用匈牙利算法,求二分图的最大匹配
                memset(st, 0, sizeof st);
                if (find(i, j)) res++;
            }
    //输出最大匹配
    cout << res << endl;
    return 0;
}