Problem Description
给你一个正方形棋盘。每个棋子可以直线攻击,除非隔着石头。现在要求所有棋子都不互相攻击,问最多可以放多少个棋子
如上述图,图1是棋盘,图2是放的最多的
思路:
可以参考博客先A了ZOJ 1654。这两题是一样的题,上述博客讲的太好了。
核心:如果把最大独立集,点的关系转换到二分图上。
本题也是二分图匹配的一个经典题目。我们将每一行,每一列被墙隔开,且包含空地的连续区域称作“块”。显然,在一个块之中,最多只能放一个机器人,我们把这些水平方向的块编上号。同样,把竖直方向的块也编上号
把每个横向块看作X部的点,竖向块看作Y部的点,若两个块有公共的空地(代表这两个块有联系,这个联系就把最大独立集,点关系转换到了二分图上),则在它们之间连边。于是,问题转化成这样的一个二分图
相当于每一个可以放棋子的点,就会有一个联系,水平方向的块,和竖直方向的块。也就是二分图,接下来就是求二分图最大匹配。
#include<bits/stdc++.h>
using namespace std;
struct node
{
int to, next;
};
char s[55][55];
int sx[55][55], sy[55][55], nx, ny;
node Map[20000];
int head[500], vis[500], match[500], cnt;
bool dfs(int u)
{
for(int i = head[u]; ~i; i = Map[i].next)
{
int to = Map[i].to;
if(!vis[to])
{
vis[to] = 1;
if(!match[to] || dfs(match[to]))
{
match[to] = u;
match[u] = to;
return 1;
}
}
}
return 0;
}
int solve()
{
int ans = 0;
memset(match, 0, sizeof(match));
for(int i = 1; i <= nx; i++)
{
if(!match[i])
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
}
return ans;
}
void add(int u, int v)
{
Map[cnt].to = v;
Map[cnt].next = head[u];
head[u] = cnt++;
}
int main()
{
int n, i, j;
while(~scanf("%d", &n) && n)
{
for(i = 0; i < n; i++) scanf("%s", s[i]);
nx = ny = 0;//有多少个水平方向的块,竖直方向的块
memset(sx, 0, sizeof(sx));//初始化,为了求水平块和竖直块的联系
memset(sy, 0, sizeof(sy));
for(i = 0; i < n; i++)//水平块
{
int flag = 0;
for(j = 0; j < n; j++)
{
if(s[i][j] == '.')
{
if(!flag) nx++;
flag++;
sx[i][j] = nx;//该点属于哪个块
}
else if(s[i][j] == 'X') flag = 0;
}
}
for(i = 0; i < n; i++)//竖直块
{
int flag = 0;
for(j = 0; j < n; j++)
{
if(s[j][i] == '.')
{
if(!flag) ny++;
flag++;
sy[j][i] = ny;//该点属于那个块
}
else if(s[j][i] == 'X') flag = 0;
}
}
cnt = 0;
memset(head, -1, sizeof(head));
for(i = 0; i < n; i++)//建二分图
{
for(j = 0; j < n; j++)
{
if(sx[i][j] && sy[i][j])//水平,竖直的块有公共部分
{
add(sx[i][j], sy[i][j]+nx);//+nx是为了防止点重复
add(sy[i][j]+nx, sx[i][j]);
}
}
}
printf("%d\n", solve());//求二分图最大匹配
}
return 0;
}