棋盘覆盖-

欢迎大家访问博客:博客传送门

棋盘覆盖


题目描述

image-20210719133614274


核心思路

在状态压缩DP的例题“蒙德里安的梦想”中,我们解决了用 1 × 2 1\times2 1×2的长方形铺满 N × M N\times M N×M网格的方案数问题。而这里与之不同的是,本题中的棋盘有禁止放置的格子,并且只需要求出能放置的 1 × 2 1\times2 1×2骨牌的最大数量即可。当 N , M N,M N,M较小时,仍然可以使用状态压缩DP来求解,这里更高效的方法则是构建出二分图匹配的模型。

二分图匹配的模型需要两个基本要素:

  • 节点能分成两个独立的集合,每个集合内部有 0 0 0条边
  • 每个节点只能与 1 1 1条匹配边相连

我们称为“0要素”和“1要素”。在把实际问题抽象成二分图匹配时,我们就要寻找题目中具有这种“0”和“1”性质的对象,从而发现模型构建的突破口。

在本题中,任意两张骨牌不能重叠,也就是每个格子只能被1张骨牌覆盖。而骨牌的大小为 1 × 2 1\times2 1×2,覆盖 2 2 2个相邻的格子。这恰好与“1要素”相对应。于是,我们可以把棋盘上没有被禁止的格子作为节点,把骨牌作为无向边(两个相邻的格子对应的节点之间连一条无向边)

我们可以发现,对于棋盘来说,相邻的节点它们的横坐标+纵坐标,得到的和 是不同的。因此,我们可以把棋盘黑白染色(行号+列号为偶数的格子被染成白色,行号+列号为奇数的格子被染成黑色),把白色格子都放进一个点集合 V 1 V_1 V1中,把黑色格子都放进另一个点集合 V 2 V_2 V2中。那么两个相同颜色的格子不可能被同一个骨牌覆盖,也就是说同色格子之间没有边相连,即点集合 V 1 V_1 V1内部没有边相连,点集合 V 2 V_2 V2内部没有边相连。这恰好与“0要素”对应。

于是,刚才建立的无向图就是一张二分图。可以把白色格子作为左部节点,黑色格子作为右部节点。如下图:

image-20210719135004377

要让骨牌不重叠的情况下尽量多放,就是求上述构建好的二分图的最大匹配。

这里因为涉及到二维坐标问题,那么我们建图时,可以使用二维的邻接矩阵g[N][N],当 g [ i ] [ j ] = 1 g[i][j]=1 g[i][j]=1表示这个格子不能放置,当 g [ i ] [ j ] = 0 g[i][j]=0 g[i][j]=0表示这个格子可以放置。

对于二分图来说,虽然是无向图。但是求二分图的最大匹配时,使用匈牙利算法,是从男生集合出发的,因此只需要当初有向边即可。那么这里我们把白色格子组成的点集合看作是男生集合,把黑色格子组成的点集合看作是女生集合。那么做匈牙利算法时,从 ( i + j ) % 2 = = 0 (i+j)\%2==0 (i+j)%2==0这个男生集合出发即可。

由于是使用邻接矩阵建图,因此不像邻接表那样的建图方式(邻接表需要add这个建图函数)


代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
typedef pair<int,int>PII;
int n,t;
//邻接矩阵
int g[N][N];
//存储女生配对的男友的编号 比如match[i][j]={2,3} 表示(i,j)这个格子所对应的那个女生她的现配男友的编号为{2,3}这个格子
PII match[N][N];
bool st[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
//匈牙利算法
bool find(int x,int y)
{
    //遍历点(x,y)的四个方向
    for(int i=0;i<4;i++)
    {
        int a=x+dx[i];
        int b=y+dy[i];
        if(a>=1&&a<=n&&b>=1&&b<=n&&!g[a][b]&&!st[a][b])
        {
            st[a][b]=true;
            PII t=match[a][b];
            if(t.first==-1||find(t.first,t.second))
            {
                match[a][b]={x,y};
                return true;
            }
        }
    }
    return false;
}
int main()
{
    scanf("%d%d",&n,&t);
    while(t--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        //如果不能放置 则g设为1
        g[x][y]=1;
    }
    memset(match,-1,sizeof match);
    int ans=0;  //匹配数量
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            //(i+j)%2==0表示男生集合  g[i][j]==0表示这个点可以放置
            if((i+j)%2==0&&g[i][j]==0)
            {
                //每一轮男生(i,j)匹配时  都要清空上一个男生进行匹配操作时所遗留下的状态
                memset(st,0,sizeof st);
                //(i,j)这个男生找到了女朋友  匹配数量+1
                if(find(i,j))
                ans++;
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值