棋盘覆盖--【二分图求最大匹配 +思路推敲代码 + 代码注释 + 保你看懂】

不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质

一、题目:[SDOI2012] 棋盘覆盖

原题链接

在一个 n × m n\times m n×m 的棋盘内,有 K K K 个方格被称为特殊方格。我们要使用一组俄罗斯方块来覆盖这个棋盘,保证特殊方格不能被覆盖,非特殊方格只能被一个俄罗斯方块覆盖,求最多能容纳的俄罗斯方块的数量。

已知有以下三组俄罗斯方块,一个棋盘可能用其中的某一组。

输入格式

第一行三个整数 n , m , K n,m,K n,m,K 和一个字符 type 为所用的俄罗斯方块组。

接下来 K K K 行每行两个整数 x , y x,y x,y 表示第 x x x 行第 y y y 列为特殊方格。

输出格式

一个整数,为所求的答案。

样例 #1

样例输入 #1

8 8 0 A

样例输出 #1

32

样例 #2

样例输入 #2

7 6 6 C
3 1
3 6
5 3
5 4
5 7
6 7

样例输出 #2

12
提示

对于测试点 1 ∼ 6 1\sim 6 16 1 ≤ n , m ≤ 100 1\le n,m\le 100 1n,m100 0 ≤ K ≤ n m 0\le K\le nm 0KnmtypeA

对于测试点 7 ∼ 12 7\sim 12 712 2 ≤ n = m ≤ 2 2 × 1 0 5 2\le n=m\le 2^{2\times 10^5} 2n=m22×105 n , m n,m n,m 2 2 2 的整数次幂, K = 1 K=1 K=1typeB

对于测试点 13 ∼ 21 13\sim 21 1321 1 ≤ n , m ≤ 11 1\le n,m\le 11 1n,m11 0 ≤ K ≤ n m 0\le K\le nm 0KnmtypeC

思路:

  1. 本题给定的是一个二维的平面矩阵。对于障碍物的位置,我们需要特殊标记下。由于骨牌只能横着占2个格子或者竖着占两个格子,所以我们可以将相邻的两个格子进行标记。
  2. 然后对于相邻的两个格子而言,其横纵坐标之和,必定是相邻的奇偶数,那么即意味着一偶一奇,一正一反,一白一黑,所以我们可以将偶数格子染成白色,奇数格子染成黑色,然后对于一个偶数格子而言,其-1或者+1的操作,必定会让一个偶数变成奇数,所以对于一个偶数格子,它的上下左右相邻的格子必定是奇数格子,也必定是黑色格子。
  3. 所以我们发现按照横纵坐标之和的奇偶性来染色,不可能存在两个白色或者两个黑色的格子相邻。而结合第一步,骨牌只能占领两个格子,而两个格子的颜色必定是相异的,不属于同一类的。则可将问题转化为:“有多少对相邻的格子,每个格子不可重复使用”,即将骨牌看作是一条边,该边可以连接一个白色格子和一个黑色格子。我们将白色格子提取到一个集合里,然后将黑色格子提取到一个集合里,构造二分图。
  4. 不让骨牌重叠就是不让两条边有重叠的点,放最多的骨牌就可以看作找到最多的边。骨牌就是两个集合的边。要求最多可以放置多少个骨牌,实际上就是让我们求最多有多少条匹配边,即求最大匹配数。

跟着思路推敲代码

  1. 首先明确一点,本题中还有特殊障碍物,且是二维平面,所以说我们应该将障碍物所在的位置进行标记,然后在匹配的时候提前避雷!
bool g[N][N];    //对于为0的点表示没有障碍物,为1的点表示有障碍物

    cin >> t;
    while (t --)
    {
        int x, y;
        cin >> x >> y;
        g[x][y] = true;
    }
  1. 然后就是,二分图的里面的对象究竟该是谁:是格子,格子是一个坐标。所以我们将坐标看作是一个整体,为其匹配另一个坐标即可!没啥特殊对待。所以我们接下来需要去枚举每一个对象,然后去为它匹配另一半,匹配成功了 ( f i n d = t r u e ) (find=true) find=true即(骨牌+1)边数 + 1;然后由于是二维的,所以我们需要枚举每个格子。不过了,我们男女匹配的时候都是枚举男生,帮助男生去找女生,那么这里呢?我们可以枚举每个偶数格子,给偶数格子物色奇数格子,所以一定要判断下:如果当前这个偶数格子成功物色到一个奇数的格子的话,说明可以放置骨牌!
    int res=0;
    for (int i=1; i <= n; i ++)
        for (int j=1; j <= n; j ++)
        {
            if (g[i][j] && (i+j)%2 == 0)
            {
                memset(st, 0, sizeof st);
                if (dfs (i, j)) res ++;
            }
        }
  1. 利用匈牙利算法开始匹配:每个点去物色对象的时候,都是提前建立好了连边的,是连好了线的,都是备胎准备好了的。而本题我们这里没有图,而对于每个格子,它所能匹配的对象,就是它的备选对象,也只有上下左右四个方向的格子。所以涉及了偏移量!
bool find(int x, int y)
{
    for (int i=0; i < 4; i ++)
    {
        int a = dx[i] + x, b = dy[i] + y;
        if (a < 1 || a > n || b < 1 || b > n) continue;
        if (st[a][b] || g[a][b]) continue;
        
        st[a][b] = true;
        PII t = match[a][b];
        if (t.x==0 || find(t.x, t.y))
        {
            match[a][b] = {x, y};
            return true;
        }
    }
    
    return false;
}

在这里插入图片描述

完整代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#define x first
#define y second

using namespace std;

const int N = 1e2 + 10;
typedef pair<int, int> PII;
bool g[N][N];    //对于为0的点表示没有障碍物,为1的点表示有障碍物
int n, t;
bool st[N][N];
//表示match[i][j] = {a, b};之所以将match定义为PII类型,是为了方便
//标记它的对象是哪个格子,因为后续发生冲突时,我们还需要根据找到它的对象,让其换一个格子匹配。
PII match[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

bool find(int x, int y)
{
    for (int i=0; i < 4; i ++)
    {
        int a = dx[i] + x, b = dy[i] + y;
        if (a < 1 || a > n || b < 1 || b > n) continue;
        if (st[a][b] || g[a][b]) continue;
        
        st[a][b] = true;
        PII t = match[a][b];
        if (t.x==0 || find(t.x, t.y))
        {
            match[a][b] = {x, y};
            return true;
        }
    }
    
    return false;
}

int main()
{
    cin >> n >> t;
    while (t --)
    {
        int x, y;
        cin >> x >> y;
        g[x][y] = true;
    }
    
    int res=0;
    for (int i=1; i <= n; i ++)
        for (int j=1; j <= n; j ++)
        {
            if (!g[i][j] && (i+j)%2 == 0)   //为偶数点找匹配的奇点
            {
                memset(st, 0, sizeof st);
                if (find (i, j)) res ++;
            }
        }
    cout << res << endl;
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值