(相当于是poj3041的进阶版,不过难度还好)
题目大概意思为将一个矩阵中的泥泞部分给覆盖起来,可以横着覆盖也可以竖着覆盖,但不能覆盖到其他草地部分,覆盖的板子长度随意,宽度为1,可以重复覆盖,求最少需要的板子数量
解题思路:把板子当成图的顶点,把泥泞的点当作连接对应板子(一个横着的,一个竖着的)的边,这样转化之后,求最少需要的板子数量,即相当于我们对于任意边,都至少需要一个该边的端点,即转为成了求最小顶点覆盖问题。
由于所有的泥泞的点,连接的都是横着的和竖着的,所以这是二分图,在二分图中,|最小顶点覆盖| = |最大匹配|,问题便迎刃而解
需要考虑的地方即如何把板子转化成顶点:
看成总共有 N * M * 2 个板子,每 i 行 j 列为顶点都有横着摆和竖着摆两种
假如有N行,M列,我们把 i * M + j 看成从 i 行 j 列开始横着摆放的板子,把 N * M + j * N + i 看成从 j 列 i 行开始竖着摆放的板子
例:
4 4
*.*.
.***
***.
..*.
其中第1行第2列(从0开始)就相当于可以从第1行第1列开始横着摆,或从第2列第0行开始竖着摆,所以把 1 * M + 1 和 N * M + 2 * N + 0 连接在一起
其他细节我就注释在代码中
#include <iostream>
#include <stdio.h>
#include <vector>
#include <string.h>
using namespace std;
int data[55][55];
int flag_x[55]; //用于记录当前第i行左边最远连续的泥泞点的位置
int flag_y[55]; //用于记录当前第j列上边最远连续的泥泞点的位置
bool used[2505];
int match[2505];
vector<int> edge[5005];
void add_edge(int from, int to)
{
edge[from].push_back(to);
edge[to].push_back(from);
}
bool dfs(int v)
{
used[v] = true;
for(int i = 0; i < edge[v].size(); i++)
{
int u = edge[v][i], w = match[u];
if(w == -1 || !used[w] && dfs(w))
{
match[u] = v;
match[v] = u;
return true;
}
}
return false;
}
int main()
{
int N, M;
char ch;
memset(flag_x, -1, sizeof(flag_x));
memset(flag_y, -1, sizeof(flag_y));
scanf("%d %d", &N, &M);
for(int i = 0; i < N; i++)
{
getchar();
for(int j = 0; j < M; j++)
{
scanf("%c", &ch);
if(ch == '*')
{
data[i][j] = 1;
}
else
{
data[i][j] = 0;
}
}
}
for(int i = 0; i < N; i++)
{
for(int j = 0; j < M; j++)
{
if(data[i][j] == 1)
{
if(flag_x[i] == -1)
{
flag_x[i] = j;
}
if(flag_y[j] == -1)
{
flag_y[j] = i;
}
add_edge(i * M + flag_x[i], N * M + j * N + flag_y[j]);
}
else
{
flag_x[i] = -1;
flag_y[j] = -1;
}
}
}
int res = 0;
memset(match, -1, sizeof(match));
for(int i = 0; i < N; i++)
{
for(int j = 0; j < M; j++)
{
memset(used, false, sizeof(used));
if(dfs(i * M + j))
{
res++;
}
}
}
printf("%d\n", res);
}