骑士放置 题解(二分图)

题目

给定一个 \(N \times M\) 的棋盘,有一些格子禁止放棋子。问棋盘上最多能放多少个不能互相攻击的骑士(国际象棋的“骑士”,类似于中国象棋的“马”,按照“日”字攻击,但没有中国象棋“别马腿”的规则)。

\(N\)\(M \leq 100\)

分析

如果我们将棋盘黑白染色,可以发现:如果在某一个点放置骑士,那么它能够攻击到的点的颜色一定和它相反。

于是满足二分图性质,可以将黑点白点各作为二分图的左右集合。

在实际打代码时,我们并不需要真的将棋盘染色,只需要知道有这个性质就可以了。

接下来我们将每一个点与它能够到达的点连边。

我们发现:如果存在两个骑士会互相攻击,当且仅当其中某一个骑士与另一骑士能够攻击到的点位置相同。

于是要想满足题目条件,我们就需要在二分图上选择最多的点,使得任意两点之间都没有一条边相连。

即这个二分图的最大独立集。

这里引入一个定理:

\(G\) 是有 \(n\) 个节点的二分图,\(G\) 的最大独立集大大小等于 \(n\) 减去最大匹配

证明网上都有,实际上也并不复杂,自己画个图就能理解。

于是这道题转化为一道传统的求二分图最大匹配。

细节

我发现部分题解在解释 n*m-t-ans 时并不是很清楚

实际上 -t 可以这么理解:

定理中说的 \(n\) 个节点在这道题实际上并不是 \(N\times M\) 个点,因为障碍的点是没有连边的

于是真正存在在二分图里的点仅仅只有 \(N\times M-t\) 个点

code

#include<bits/stdc++.h>
#define Hash(i,j) (i*m+j)
using namespace std;
const int N=1e5+10;
const int maxn=110;
struct node
{
	int nex;
	int to;
}a[N];
int tot,head[N];
void add(int u,int v)
{
	a[++tot].nex=head[u];
	head[u]=tot;
	a[tot].to=v;
}
int g[N];//记录障碍物 
int n,m,t;
int fx[10]={-2,-1,1,2,2,1,-1,-2};//骑士能够走到的点打表(fx和fy),遍历每一个点 
int fy[10]={-1,-2,-2,-1,1,2,2,1};
int vis[N];
int match[N];
bool dfs(int u)//匈牙利算法 
{
	for(int i=head[u];i;i=a[i].nex)
	{
		int v=a[i].to;
		if(vis[v]) continue;
		vis[v]=1;
		if(!match[v]||dfs(match[v])) return match[v]=u,1;
	}
	return 0;
}
int main()
{
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1,x,y;i<=t;i++)
	{
		scanf("%d%d",&x,&y);
		g[Hash(x,y)]=1;
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(g[Hash(i,j)]) continue;//障碍物不算 
			for(int k=0;k<8;k++)
			{
				int xx=i+fx[k];
				int yy=j+fy[k];
				if(xx<1||yy<1||xx>n||yy>m||g[Hash(xx,yy)]) continue;
				add(Hash(i,j),Hash(xx,yy));
			}
		}
	}
	int ans=0;
	for(int i=Hash(1,1);i<=Hash(n,m);i++)//求二分图最大匹配 
	{
		if(g[i]) continue;//障碍物不管
		memset(vis,0,sizeof vis);
		if(dfs(i)) ans++;
	}
	printf("%d",n*m-t-ans/2);//因为点会算重,所以ans会除以2 
	return 0;
}
posted @ 2024-01-25 20:07  inlinexhx  阅读(3)  评论(0编辑  收藏  举报