欢迎大家访问博客:博客传送门
棋盘覆盖
题目描述
核心思路
在状态压缩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要素”对应。
于是,刚才建立的无向图就是一张二分图。可以把白色格子作为左部节点,黑色格子作为右部节点。如下图:
要让骨牌不重叠的情况下尽量多放,就是求上述构建好的二分图的最大匹配。
这里因为涉及到二维坐标问题,那么我们建图时,可以使用二维的邻接矩阵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;
}