杭电 hdu 1507 Uncle Tom's Inherited Land* (二分匹配 + 两种方法构造边)
Uncle Tom's Inherited Land*
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 2329 Accepted Submission(s): 961
Special Judge
Your uncle Tom wants to sell the inherited land, but local rules now regulate property sales. Your uncle has been informed that, at his great-great-uncle's request, a law has been passed which establishes that property can only be sold in rectangular lots the size of two squares of your uncle's property. Furthermore, ponds are not salable property.
Your uncle asked your help to determine the largest number of properties he could sell (the remaining squares will become recreational parks).
![](https://i-blog.csdnimg.cn/blog_migrate/59be5e7b0ceb44fb26011da271f849a5.gif)
4 4 6 1 1 1 4 2 2 4 1 4 2 4 4 4 3 4 4 2 3 2 2 2 3 1 0 0
4 (1,2)--(1,3) (2,1)--(3,1) (2,3)--(3,3) (2,4)--(3,4) 3 (1,1)--(2,1) (1,2)--(1,3) (2,3)--(3,3)
题意:有一块广场,被分成很多个小方格,这一整块广场中,有很多的池塘,池塘是1*1的格式,其它的就是空出来的地,现在想利用空出来的地,进行价值利用,但是这个利用要符合
大小是1*2的格式,问最多有多少1*2格式的空地来被价值利用
首先输入n,m,表示整块广场的大小
接着是t,表示有多少个池塘
然后是t行x,y,表示每个池塘的位置
然后输出每一个1*2格式的空地的位置(x1,y1)--(x2,y2)
题解:这道题目的思路在于,格子的选择是1*2,也就是可以这样想,一个格子出发,它只能从上下左右四个格子中选择一个,这样就符合了不重合的情况。
然后这道题目就成为了二分匹配,对于一个格子,选择四种相邻格子的一个来组成1*2格式,同事要解决相邻的格子不能做两次这样的选择,不然会出错,
所有我们选择基础格子要是那种几个格子不能成为对方格子的上下左右中的一个,所有基础格子就选择i+j为偶数的格子来当;
然后二分匹配一下,找最大的匹配数,这就是最大所能求的1*2格式的空地利用个数
这里有两种构造边的方法;
方法一:将所有的i+j为偶数(舍去池塘)当作是x,然后将所有i+j为奇数的(舍去池塘)当作是y,这样构造边
然后就是遇到一个偶数就加1,相同,遇到一个奇数也加1,这样每个格子就有其独立的数字来标记了,当然要记录每个标记数字代表的广场上的位置,用
结构体rep[]来记录,这样再通过二分匹配就可以遍历找到最大匹配数了,而最后的输出空地的位置,也是可以的,因为link[]里面记录了i+j为偶数的点的选择格子
这样就可以用个rep[]来最后输出空地的位置
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 111;
int n, m, num1, num2;
int flag[maxn][maxn]; //记录广场上每个坐标的代表情况,主要是记录池塘位置
int map[maxn][maxn]; //记录奇数点和偶数点的可否连接关系,0表示不能连接,1表示可以连接
int link[maxn]; //记录link[i] = x,i这样的偶数点被x这个奇数点所选择
int vis[maxn];
int foot[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; //上下左右四步
struct node{ //记录每个奇偶数点数字标记所代表的广场坐标
int x1, y1;
int x2, y2;
}rep[10005];
void Search(int x, int y){ //将奇偶数点是否可以选择连接进行标记,在map[][]中表示
int i;
for (i = 0; i < 4; ++i){ //上下左右都进行判断
int xn = x + foot[i][0];
int yn = y + foot[i][1];
if (xn >= 1 && xn <= n && yn >= 1 && yn <= m && flag[xn][yn] != -1)
map[flag[x][y]][flag[xn][yn]] = 1;
}
}
bool Find (int x){ //基础的Find()函数,确定最后的最大匹配数
int i;
for (i = 1; i < num2; ++i){
if (!vis[i] && map[x][i]){
vis[i] = 1;
if (link[i] == -1 || Find (link[i])){
link[i] = x;
return true;
}
}
}
return false;
}
int main (){
int i, j, t, a, b;
while (scanf ("%d%d", &n, &m) != EOF){
if (n == 0 && m == 0) break;
scanf ("%d", &t);
memset (flag, 0, sizeof (flag));
memset (link, -1, sizeof (link));
memset (map, 0, sizeof (map));
for (i = 0; i < t; ++i){
scanf ("%d%d", &a, &b);
flag[a][b] = -1; //池塘位置的标记
}
num1 = 1;
num2 = 1;
for (i = 1; i <= n; ++i){
for (j = 1; j <= m; ++j){
if (!flag[i][j]){
if ((i + j) % 2 == 0){ //对偶数点进行数字标记
flag[i][j] = num1;
rep[num1].x1 = i;
rep[num1++].y1 = j;
}
else{ //对奇数点进行数字标记
flag[i][j] = num2;
rep[num2].x2 = i;
rep[num2++].y2 = j;
}
}
}
}
for (i = 1; i <= n; ++i){
for (j = 1; j <= m; ++j){
if (flag[i][j] != -1){
if ((i + j) % 2 == 0)
Search(i, j); //对是否可以连接的奇偶数进行判断和标记
}
}
}
int ant = 0;
for (i = 1; i < num1; ++i){
memset (vis, 0, sizeof (vis)); //寻找最大匹配数
if (Find (i))
ant++;
}
printf ("%d\n", ant);
for (i = 1; i < num2; ++i){ //输出每个1*2规格的空地的位置,通过link[]来查询
if (link[i] != -1){
printf ("(%d,%d)--(%d,%d)\n", rep[link[i]].x1, rep[link[i]].y1, rep[i].x2, rep[i].y2);
}
}
}
return 0;
}
方法二:通常的二分匹配是分成两组,然后通过单一的数字进行标记,这样在遍历的时候就可以很容易的区分两组,或者说是第一组的对应组,其实,要是对应组
很好分辨的话,就不用这样进行单一的数字标记了。
这次采用的坐标点进行分组,同样是分成i+j为偶数的点和i+j为奇数的点进行分类,但是却不用进行数字标记,就拿(x,y)这样的坐标来当做分组,而对应的奇数可以直接找到
就不用再分组了,在Find()的时候直接遍历上下左右四个点的坐标就可以了
然后通过link[x1][y1] = (x2,y2),这样来记录最后的匹配
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 111;
int n, m;
int map[maxn][maxn]; //记录空地和池塘的位置
int vis[maxn][maxn];
int foot[4][2] = {1, 0, -1, 0, 0, 1, 0, -1}; //上下左右四个方向
struct node{
int x, y;
}link[maxn][maxn]; //记录偶数点的匹配奇数点的坐标
bool Find (int x, int y){
int i;
for (i = 0; i < 4; ++i){ //这是通过上下左右的查找匹配
int xn = x + foot[i][0];
int yn = y + foot[i][1];
if (xn >= 1 && xn <= n && yn >= 1 && yn <= m && map[xn][yn] != -1 && !vis[xn][yn]){
vis[xn][yn] = 1;
if (link[xn][yn].x == -1 || Find(link[xn][yn].x, link[xn][yn].y)){
link[xn][yn].x = x;
link[xn][yn].y = y;
return true;
}
}
}
return false;
}
int main (){
int i, j, t, a, b;
while (scanf ("%d%d", &n, &m) != EOF){
if (n == 0 && m == 0)break;
scanf ("%d", &t);
memset (map, 0, sizeof (map));
memset (link, -1, sizeof (link));
for (i = 1; i <= t; ++i){ //记录池塘和空地的位置
scanf ("%d%d", &a, &b);
map[a][b] = -1; //-1表示池塘的位置
}
int ant = 0;
for (i = 1; i <= n; ++i){
for (j = 1; j <= m; ++j){
if (map[i][j] != -1){
if ((i + j) % 2 == 0){
memset (vis, 0, sizeof (vis));
if (Find(i, j)) //找到偶数点,并且不是池塘,就进行匹配搜索
ant++;
}
}
}
}
printf ("%d\n", ant);
for (i = 1; i <= n; ++i){
for (j = 1; j <= m; ++j){
if (map[i][j] != -1){
if ((i + j) % 2 == 1){
if (link[i][j].x != -1) //通过link[][]来找到每个匹配组的位置坐标,然后输出
printf ("(%d,%d)--(%d,%d)\n",link[i][j].x, link[i][j].y, i, j);
}
}
}
}
printf ("\n");
}
return 0;
}