AcWing 372. 棋盘覆盖

AcWing 372. 棋盘覆盖

一、题目描述

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

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

输入格式
第一行包含两个整数 Nt,其中 t 为禁止放置的格子的数量。

接下来 t 行每行包含两个整数 xy,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。

输出格式
输出一个整数,表示结果。

数据范围
1N100,0t100

输入样例

8 0

输出样例

32

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

前置知识

三、题目分析

这题乍一看是状压DP,但是题目数据范围是100比较大,所以要考虑别的思路,由于卡片只能放到相邻的两个格子当中,我们把每个格子看作一个点,相邻两个格子连出一条边,于是这个题就抽象成了最多选多少条边,所有选出的边之间没有公共点,这就是最大匹配问题。
就比如下面这个图,黑色区域是禁止放置的

经过匹配之后:

求最大匹配问题可以用匈牙利算法求解,但是用匈牙利算法前提需要图是二分图,所以我们需要判断一下是不是二分图。

一个nn的矩阵,我们通过染色把黑色区域看作一个集合,白色区域看作一个集合。
两个集合当中每个点相邻的点的颜色都是不同的,通过染色法判定我们发现这就是一个二分图。而且黑色区域每个点坐标和为偶数,白色区域每个点坐标和为奇数,因此我们就可以用匈牙利算法进行求解最大匹配问题。

实现步骤:

  • 建图(标记禁止放置的点)
  • 对二分图中的白色区域集合进行匹配并且更新答案
  • 输出最大匹配数

考虑一个格子(i,j):

  • i+j为偶数:记为 黑格子
  • i+j为奇数:记为 白格子

如果这个白格子没有被禁止,那么就让它向周围没有被禁止的黑格子连有向边,表示:
如果选择这条边(在这两个格子上放骨牌)会对答案有1的贡献

显然白格子周围都是黑格子,所以白格子之间不会有边.那么这就是一个 二分图最大匹配的模型 ,跑一下就好了.

时间复杂度
最多O(n2)个点,O(n2)条边,所以时间复杂度O(n4)

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 40010;

int n, m;

int dx[] = {-1, 0, 1, 0}; // 上右下左
int dy[] = {0, 1, 0, -1}; // 上右下左

int g[N]; //一维的转换后表示,如果手欠,想写成二维的,一定要注意不能写N,要写110,否则会SE,不要问我是怎么知道的!

// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

// 匈牙利算法模板
int match[N], st[N];
int find(int u) {
    for (int i = h[u]; ~i; i = ne[i]) {
        int v = e[i];
        if (st[v]) continue;
        st[v] = 1;
        if (match[v] == -1 || find(match[v])) {
            match[v] = u;
            return 1;
        }
    }
    return 0;
}

int main() {
    memset(h, -1, sizeof h);         // 初始化链式前向星
    memset(match, -1, sizeof match); // 初始化匈牙利算法的匹配标识数组

    scanf("%d %d", &n, &m);

    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        // 因为要做坐标变换,需要下标从0开始,本题下标从1开始,采用减一大法
        a--, b--;
        g[a * n + b] = 1;
    }

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) {
            if (g[i * n + j]) continue;     // 出发点有障碍物不可以
            if ((i + j) % 2 == 0) continue; // 出发点是黑色点不可以

            for (int k = 0; k < 4; k++) {
                int tx = i + dx[k], ty = j + dy[k];
                if (g[tx * n + ty]) continue;                         // 目标点有障碍物
                if (tx < 0 || tx == n || ty < 0 || ty == n) continue; // 目标点出界
                add(i * n + j, tx * n + ty);                          // 加边
            }
        }

    int res = 0;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if ((i + j) % 2 == 1) {
                memset(st, 0, sizeof st);
                res += find(i * n + j);
            }

    printf("%d\n", res);
    return 0;
}
posted @   糖豆爸爸  阅读(134)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-04-05 MySQL性能测试--分区和分表测试
Live2D
点击右上角即可分享
微信分享提示