题目:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=654
题意:
‘o’, ‘*’, ‘#’-> 空地, 草地, 墙壁
只有空地可以放机器人,且机器人会向四个方向开炮; 草地可以被炮穿过;墙壁能阻挡炮弹
求机器人不会相互攻击下的,最大机器人数目
分析:
刚开始,考虑到每个空地作为一个点,可以相互攻击的点,连上线,变成无向图后,问题就装换成了:从所有点中选出尽可能多的点,且选出的点不能相连 —— 最大独立点集
思路是清晰了,不过对于实现出了问题,没有有效的算法能求解最大独立点集,又因为:最大独立点集 = 总节点数 - 最大匹配
所以接下来开始构造一个关于最大匹配(边的最大独立数)的模型即可,由于是二维的地图,一般把行和列分开为x集合、y集合的二部图处理
最后用匈牙利算法求出二部图最大匹配得解
关于二部图的构建,例如:
5*5 mapt
o * * * #
* # # # *
o o # o o
* * * # o
# o * * o
首先,先把连续的一行(炮弹可以直接穿过点)进行分块,标记1~n
5*5 mapx
1 0 0 0 0
0 0 0 0 0
2 2 0 3 3
0 0 0 0 4
0 5 0 0 5
然后,把连续的一列也进行分块,标记为1~m
5*5 maxpy
1 0 0 0 0
0 0 0 0 0
1 2 0 3 4
0 0 0 0 4
0 2 0 0 4
最后,把分块之后的矩阵,对比,找出行列分块后的公共的空地,相连起来
n*m Edge[u][v](邻接矩阵:表示水平u行,竖直v列,有公共空地)
1 0 0 0
1 1 0 0
0 0 1 1
0 0 0 1
0 1 0 1
每条边,表示一块空地(一块横向+一块纵向),冲突的空地,则必有公共顶点
所有问题就转换成了:在二部图中,找没有公共顶点的最大边集 —— 最大匹配问题
代码:
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <string>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <map>
using namespace std;
#define MAX 51
#define MIN -1e9
#define INF 0x7f7f7f7f
int t, n, m; // 组数, 行列数
int x[MAX*MAX], y[MAX*MAX]; // 行列节点匹配情况
int Edge[MAX*MAX][MAX*MAX]; // x、y集合关联矩阵
int vis[MAX*MAX]; // path函数DFS时的防死循环数组
int Case; // 组数
int path(int u) // 查找增广路径
{
for(int v = 1; v<=m; v++) // 每次xu, 都遍历所有y
{
if(vis[v] == 0 && Edge[u][v]) // 该次dfs未访问过的y && xu -> yv
{
vis[v] = 1;
if(y[v] == -1 || path(y[v])) // yv 未被匹配 || 已经匹配,但是匹配yv的x可以更新匹配
{
x[u] = v;
y[v] = u;
return 1;
}
}
}
return 0;
}
void solve()
{
memset(x, -1, sizeof(x)); // 初始x、y均未匹配
memset(y, -1, sizeof(y));
int ans = 0;
for(int u = 1; u<=n; u++) // 遍历所有x
{
if(x[u] == -1)
{
memset(vis, 0, sizeof(vis));
ans += path(u); // 增广路成功 -- 1 失败 -- 0
}
}
printf("Case :%d\n", Case ++);
printf("%d\n", ans);
}
int main()
{
int i, j;
//freopen("a.txt", "r", stdin);
scanf("%d", &t);
Case = 1;
while(t--)
{
int nt, mt;
char mapt[MAX][MAX]; // 存放字符数组
int mapx[MAX][MAX], mapy[MAX][MAX]; // 存放分块后的值(行分块、列分块)
memset(mapx, 0, sizeof(mapx));
memset(mapy, 0, sizeof(mapy));
scanf("%d%d", &nt, &mt);
for(i = 0; i<nt; i++)
{
scanf("%s", mapt[i]);
}
int flag; // 分块标记
n = 0; // 行分块的个数为 n(x集合元素个数)
for(i = 0; i<nt; i++) // 行分块
{
flag = 0; // 换行,更新分块标记
for(j = 0; j<mt; j++)
{
if(mapt[i][j] == 'o')
{
if(flag == 0) n++; // 分块标记为0,则块数 +1
mapx[i][j] = n; flag = 1; // 设置分块标记为1,知道需要更新分块
}
else if(mapt[i][j] == '#') flag = 0; // 遇到墙,更新分块标记
}
}
m = 0; // 列分块的个数 m(y集合元素个数)
for(j = 0; j<mt; j++) // 列分块
{
flag = 0;
for(i = 0; i<nt; i++)
{
if(mapt[i][j] == 'o')
{
if(flag == 0) m++;
mapy[i][j] = m; flag = 1;
}
else if(mapt[i][j] == '#') flag = 0;
}
}
memset(Edge, 0, sizeof(Edge));
for(i = 0; i<nt; i++) // 查找公共空地
{
for(j = 0; j<mt; j++)
{
if(mapx[i][j] * mapy[i][j] != 0) // 行块 -> 列块 重合
{
Edge[mapx[i][j]][mapy[i][j]] = 1;
}
}
}
solve(); // 匈牙利 -> 最大匹配数
}
return 0;
}