不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
一、题目:[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
1∼6,
1
≤
n
,
m
≤
100
1\le n,m\le 100
1≤n,m≤100,
0
≤
K
≤
n
m
0\le K\le nm
0≤K≤nm,type
为 A
;
对于测试点
7
∼
12
7\sim 12
7∼12,
2
≤
n
=
m
≤
2
2
×
1
0
5
2\le n=m\le 2^{2\times 10^5}
2≤n=m≤22×105,
n
,
m
n,m
n,m 为
2
2
2 的整数次幂,
K
=
1
K=1
K=1,type
为 B
;
对于测试点
13
∼
21
13\sim 21
13∼21,
1
≤
n
,
m
≤
11
1\le n,m\le 11
1≤n,m≤11,
0
≤
K
≤
n
m
0\le K\le nm
0≤K≤nm,type
为 C
。
思路:
- 本题给定的是一个二维的平面矩阵。对于障碍物的位置,我们需要特殊标记下。由于骨牌只能横着占2个格子或者竖着占两个格子,所以我们可以将相邻的两个格子进行标记。
- 然后对于相邻的两个格子而言,其横纵坐标之和,必定是相邻的奇偶数,那么即意味着一偶一奇,一正一反,一白一黑,所以我们可以将偶数格子染成白色,奇数格子染成黑色,然后对于一个偶数格子而言,其-1或者+1的操作,必定会让一个偶数变成奇数,所以对于一个偶数格子,它的上下左右相邻的格子必定是奇数格子,也必定是黑色格子。
- 所以我们发现按照横纵坐标之和的奇偶性来染色,不可能存在两个白色或者两个黑色的格子相邻。而结合第一步,骨牌只能占领两个格子,而两个格子的颜色必定是相异的,不属于同一类的。则可将问题转化为:“有多少对相邻的格子,每个格子不可重复使用”,即将骨牌看作是一条边,该边可以连接一个白色格子和一个黑色格子。我们将白色格子提取到一个集合里,然后将黑色格子提取到一个集合里,构造二分图。
- 不让骨牌重叠就是不让两条边有重叠的点,放最多的骨牌就可以看作找到最多的边。骨牌就是两个集合的边。要求最多可以放置多少个骨牌,实际上就是让我们求最多有多少条匹配边,即求最大匹配数。
跟着思路推敲代码
- 首先明确一点,本题中还有特殊障碍物,且是二维平面,所以说我们应该将障碍物所在的位置进行标记,然后在匹配的时候提前避雷!
bool g[N][N]; //对于为0的点表示没有障碍物,为1的点表示有障碍物
cin >> t;
while (t --)
{
int x, y;
cin >> x >> y;
g[x][y] = true;
}
- 然后就是,二分图的里面的对象究竟该是谁:是格子,格子是一个坐标。所以我们将坐标看作是一个整体,为其匹配另一个坐标即可!没啥特殊对待。所以我们接下来需要去枚举每一个对象,然后去为它匹配另一半,匹配成功了 ( 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 ++;
}
}
- 利用匈牙利算法开始匹配:每个点去物色对象的时候,都是提前建立好了连边的,是连好了线的,都是备胎准备好了的。而本题我们这里没有图,而对于每个格子,它所能匹配的对象,就是它的备选对象,也只有上下左右四个方向的格子。所以涉及了偏移量!
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;
}