P1569 【线性规划与网络流24题#24】骑士共存
时间限制 : 10000 MS 空间限制 : 65536 KB
问题描述
0 X 0 X 0
X 0 0 0 X
0 0 S 0 0
X 0 0 0 X
0 X 0 X 0
在一个n*n个方格的国际象棋棋盘上,马(骑士)可以攻击的棋盘方格如图所示。棋盘上某些方格设置了障碍,骑士不得进入。(S代表骑士的位置,X代表他可以攻击到的点)
对于给定的n*n个方格的国际象棋棋盘和障碍标志,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击。
输入格式
第一行有2 个正整数n 和m (1<=n<=200, 0<=m分别表示棋盘的大小和障碍数。接下来的m 行给出障碍的位置。每行2 个正整数,表示障碍的方格坐标。
输出格式
一个整数,计算出的共存骑士数
样例输入
3 2
1 1
3 3
样例输出
5
来源 二分图
这是个假的国际象棋
题解
这道题目建图是真的神奇
因为它并不是为了求最大匹配而建图
而是为了求最大独立集…
引入概念
所谓最大独立集,就是求最多个相互之间没有边连接的点
比如 1-2 2-3
假如我们删去2这个点
那么剩下的 1 3之间是没有边连接的
同时这样也是我们所能求到的最多的独立的点数
那么1 3就是这个图的最大独立集
关于最大独立集,有一个结论(目前我还不想证(主要是证不到,说起也很难意会))
最大独立集内点的个数=所有的点数-最大匹配边数
建图
我们比较容易(假的)想到的就是一个骑士和他所能攻击到的点分置两边
这个样子每一条边对应的是一个攻击关系
而我们要做的就是求这个图的最大独立集
也就是说,尽量少地删除点,达到把所有边都删除的目的
为什么要求这些呢??
因为当我们把所有的边都删除之后,剩下的点之间就不构成了攻击关系
那么这些点就是可以共存的
建图注意
这道题目是很容易重复存边的
为什么呢
比如下图
10
00
02
我们如果依次读点进行判断
那么我们会加入两条边
1-2
2-1
也就是说,分边之后会有
1 2
| |
2 1
这个图的最大独立集是2(删除下面两个点)
这个答案显然是错的
因为1-2和2-1表达的是同一个攻击关系,我们实际上只需要且仅能存一条边
那么我们就需要一个判断方式,避免重复存边
于是,我们就想到了染色法建图
无重复的建图方式
我们对一个图做标记
0 1 0 1 0 0 X 0 X 0
1 0 1 0 1 对应 X 0 0 0 X
0 1 0 1 0 ——————> 0 0 S 0 0
1 0 1 0 1 X 0 0 0 X
0 1 0 1 0 0 X 0 X 0
不难发现,骑士所在的点的颜色是0,而他所能攻击到的点所在的颜色都是1
所以我们在存边的时候,只存骑士所在的点能够攻击到的点连成的边
进一步扩展:
所有能够攻击到某个颜色为0的点的颜色都是1
所以我们只需要读取颜色为1的点(或者颜色为0的点)存边,即可避免重复
而且我们不会漏存
假设存在某个颜色为0的点能够攻击到1
那么1也一定能够攻击到这个0
绝对不存在0能够攻击到1而1攻击不到0的情况
于是我们就能够的到结果了
我们用染色法建图(详见1521),然后删去障碍点
对某一种颜色的点进行存边,求出最大匹配数
根据结论,求出最大独立集即可
附上代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,m,res=0,fath[45678];
int maps[234][234],all=0,star[45678],ent[45678],nxt[45678];
bool went[45678];
void add(int s,int e)
{
nxt[++all]=star[s];
star[s]=all;
ent[all]=e;
}
bool find(int s)
{
int e,bian;
for(bian=star[s],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
if(!went[e])
{
went[e]=1;
if(!fath[e]||find(fath[e]))
{
fath[e]=s;
return 1;
}
}
return 0;
}
int main()
{
int A=1,B=1,a,b;
scanf("%d%d",&n,&m);
for(int a=1;a<=n;a++)
for(int b=1;b<=n;b++)maps[a][b]=(a+b%2==0)?A++:B++;//科学染色
while(scanf("%d%d",&a,&b)!=EOF)maps[a][b]=0;//删点
for(int a=1;a<=n;a++)
for(int b=1+a%2;b<=n;b=b+2)//交替读取,只读取相同颜色的点
if(maps[a][b])
{
if(a-1>0)
{
if(b-2>0&&maps[a-1][b-2])add(maps[a][b],maps[a-1][b-2]);
if(b+2<=n&&maps[a-1][b+2])add(maps[a][b],maps[a-1][b+2]);
}
if(a-2>0)
{
if(b-1>0&&maps[a-2][b-1])add(maps[a][b],maps[a-2][b-1]);
if(b+1<=n&&maps[a-2][b+1])add(maps[a][b],maps[a-2][b+1]);
}
if(a+1<=n)
{
if(b-2>0&&maps[a+1][b-2])add(maps[a][b],maps[a+1][b-2]);
if(b+2<=n&&maps[a+1][b+2])add(maps[a][b],maps[a+1][b+2]);
}
if(a+2<=n)
{
if(b-1>0&&maps[a+2][b-1])add(maps[a][b],maps[a+2][b-1]);
if(b+1<=n&&maps[a+2][b+1])add(maps[a][b],maps[a+2][b+1]);
}
}//科学存边
for(int a=1;a<=n;a++)
for(int b=1+a%2;b<=n;b=b+2)//相同颜色的点在一边,所以我们只读取相同颜色的点
if(maps[a][b])
{
memset(went,0,sizeof(went));
if(find(maps[a][b]))res++;
}
printf("%d",n*n-m-res);
}