Time Limit:50000MS Memory Limit:65536K
Total Submit:322 Accepted:110
Case Time Limit:10000MS
Description
We are given a chess-board of size nn, from which some fields have been removed. The task is to determine the maximum number of knights that can be placed on the remaining fields of the board in such a way that none of them check each other.
一张大小为nn的国际象棋棋盘,上面有一些格子被拿走了,棋盘规模n不超过200。马的攻击方向如下图,其中S处为马位置,标有X的点为该马的攻击点。
Fig.1: A knight placed on the field S checks fields marked with x.
Write a program, that:
reads the description of a chess-board with some fields removed, from the input file kni.in,
determines the maximum number of knights that can be placed on the chess-board in such a way that none of them check each other,
writes the result to the output file kni.out.
你的任务是确定在这个棋盘上放置尽可能多的马,并使他们不互相攻击。
Input
The first line of the input file kni.in contains two integers n and m, separated by a single space, 1<=n<=200, 0<=m<n2; n is the chess-board size and m is the number of removed fields. Each of the following m lines contains two integers: x and y, separated by a single space, 1<=x,y<=n – these are the coordinates of the removed fields. The coordinates of the upper left corner of the board are (1,1), and of the bottom right are (n,n). The removed fields are not repeated in the file.
Output
The output file kni.out should contain one integer (in the first and only line of the file). It should be the maximum number of knights that can be placed on the given chess-board without checking each other.
Sample Input
3 2
1 1
3 3
Sample Output
5
解题思路
对于本题来说,相互攻击的位置肯定不能同时存在两个马。
如果我们把两个相互攻击的位置连一条边,从而构成一个图。
那么相邻的两个点不能同时选,也就是求最大独立集。
最大独立集:
定义:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。找出一个包含顶点数最多的独立集称为最大独立集。
方法:最大独立集=所有顶点数-最小顶点覆盖。
定理:U=V-M;V=顶点数,M=最大匹配数(最小覆盖数),U=最大独立集。
在上面这个图中最小顶点覆盖 = 3 =3 =3,即 2 , 4 , 7 2,4,7 2,4,7构成最小顶点覆盖。
则其他点6个构成最大独立集。且其他点不可能相连。
假设其他点相连则这条边必定没有被2,4,7 覆盖,与 2 , 4 , 7 2,4,7 2,4,7是最小顶点覆盖矛盾。因此其他点之间必定没有边。而 2 , 4 , 7 2,4,7 2,4,7是最小顶点覆盖,所谓最小就是不能再小了,因此我们的独立集就是最大了。
ans=顶点数-拿去的格子-最大匹配
建模:
考虑对棋盘进行黑白染色。
那么当 [ ( i + j ) [(i+j)%2==0] [(i+j)时,该点为黑色,否则该点为白色。
一个很显然的性质,相互攻击的两个位置颜色一定不同。我们把黑点放在左边,白点放在右边,那么这个图一定是二分图。
首先我们把点进行编号。第i行j列的格子被编号为 ( i − 1 ) ∗ m + j (i-1)*m+j (i−1)∗m+j。
因为连的是无向图,所以我们只从黑点出发连向白点
代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int dx[9]={0,1,2,1,2,-1,-1,-2,-2};
const int dy[9]={0,2,1,-2,-1,2,-2,1,-1};
long long k,m,n;
int x,y,b[205][205],v[205][205],h[40200];
long long kk,ans,use[40200],boy[40200];
struct c{
int x,next;
}a[402005];
int find(int x){
for(int i=h[x];i;i=a[i].next)
{
int t=a[i].x;
if(use[t]) continue;
use[t]=1;
if(boy[t]==0||find(boy[t]))
{
boy[t]=x;
return 1;
}
}
return 0;
}//最大匹配
void add(int x,int y){
a[++kk].x=y;
a[kk].next=h[x];
h[x]=kk;
}
bool check(int x,int y){
return (x>0&&y>0&&x<=n&&y<=n);
}
int main(){
scanf("%lld%lld",&n,&m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
v[i][j] = (i - 1) * n + j;//求每个点位置的编号
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
b[x][y]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if((b[i][j])&&(i+j)%2) continue;//如果这个点被拿走直接跳过,黑点就加边
for(int k=1;k<=8;k++)
{
int x=i+dx[k],y=j+dy[k];
if(check(x,y)&&!b[x][y])//如果这个点被拿走还是跳过
add(v[i][j],v[x][y]);
}
}
}
for(int i=1;i<=n*n;i++)
{
memset(use,0,sizeof(use));
ans+=find(i);
}
printf("%lld",n*n-m-ans);
}