P3882 [JLOI2008]将军 (二分图)

题目链接

前置知识

P2825 [HEOI2016/TJOI2016]游戏
注:本题做法与上题做法类似
用到的算法:

  • 图论——二分图——匈牙利算法(最大匹配)
  • 建边——链式前向星。

解题思路

题目要求输出在题目给定的棋盘上能放最多的bishop(象)。
因为象的行走路线为对角线(斜线),不好写,所以我们将整个棋盘顺时针旋转45°(如图)。

由图可以看出,原先棋盘的对角线在顺时针旋转45°后变成了行和列。
由此,我们考虑行列匹配

一、输入

这里有一个巨坑点(作者这个蒟蒻被这个东西坑了一周)。
输入原棋盘时,建议一行一行的输入并且输入整个棋盘后再进行操作,否则有可能“听取WA声一片”。
红红火火WA

二、存储旋转后的棋盘

我们建立一个结构体 n o d e node node

struct node{
	int xx,yy;
	int can; 
}a[2200][2200];

a [ i ] [ j ] . x x a[i][j].xx a[i][j].xx 表示在原棋盘中 i i i j j j 列的格子在旋转后位于 x x xx xx 行。
a [ i ] [ j ] . y y a[i][j].yy a[i][j].yy 表示旋转后位于 y y yy yy 列。

c a n can can 的值分三种:

  • c a n can can = 0
    该点可以放置象,且不会攻击到棋盘上原有棋子。
  • c a n can can = 1
    该点已有棋子,不可放置象,会阻挡除马以外的攻击范围。
  • c a n can can = 2
    该点未有棋子,但不可放置象,否则会攻击原有棋子或被原有棋子攻击,但不阻挡任何棋子的攻击范围

接下来,我们看具体如何操作。

1. 旋转后的格子位置

我们假设 n n n = 4 , m m m = 5。
先观察原棋盘格子的位置。

再看旋转后格子的位置。

不难发现,棋盘旋转45°后,原位置为 ( i , j ) (i,j) (i,j) 的格旋转到了 ( i + j − 1 , j + n − i ) (i+j-1,j+n-i) (i+j1,j+ni) 的位置。


2.标记攻击范围

国际象棋中一共有 6 种棋子:
K – king(国王),Q – queen(皇后),B – bishop(象,教主),N – knight(马、骑士),R – rook(车),P – pawn(兵)。

在原棋盘上,棋子的攻击范围分别如下:
攻击范围
我们将棋子所在格子的 c a n can can 标记为 1 1 1 , 将攻击范围标记为 2 2 2
注意:除马(knight)以外,其他棋子的攻击范围均会被棋子阻挡
攻击范围被阻挡的例子
如上图,车(R)的攻击范围被 兵(P) 阻挡,无法攻击到绿色格子。

你以为这道题的思路到此就结束了?不,还没有。
再细读题目,发现题目有这样一句话:

你需要避免的有 2 种情况: 你摆放的 bishop 之间的互相攻击以及你摆放的 bishop 与预先摆放好的棋子之间的互相攻击

所以我们不仅要标记棋子原有的攻击范围,还要再加上一个象的攻击范围。

三、二分图最大匹配

在【一】里讲过,旋转45°后,象的攻击范围由对角线变为行和列。由此,我们可以行列匹配。
我们开两个数组 r r r c c c
r r r 数组存储象在新棋盘的水平方向上的每一个不同的攻击范围
c c c 数组存储象在新棋盘的竖直方向上的每一个不同的攻击范围
下面举个简单的例子来进一步理解。
红色点为 N N N 的攻击范围
我们设新棋盘的水平方向上不同的攻击范围的标记为 l e n r lenr lenr

  1. l e n r lenr lenr 每过一行需要加1,因为象在新棋盘上水平方向只能攻击到一行
  2. 如果遇到棋子, l e n r lenr lenr 也需要加1,因为棋子能阻挡象的攻击范围

则完成标记后的 r r r 数组如下:
r
标记 c c c 数组时同理,不作赘述。
下图为标记完成的 c c c 数组。
c
标记完成后,遍历新棋盘上的每一个点,若该点 ( i , j ) (i,j) (i,j) 可以放置象,则建立边连接 r i , j r_{i,j} ri,j c i , j c_{i,j} ci,j (连接在该位置的象所能攻击到的两个方向的攻击范围编号)。

最后用匈牙利算法求最大匹配,就顺利切下这道题啦 !


下面结合代码来理解吧!

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
int head[1050000],to[1050000],ne[1050000],id;
int vis[20200],d[20200],n,m,maxx,ans;
int mp[2200][2200],r[2200][2200],c[2200][2200];
char s[1050][1050];
struct node{
	int xx,yy;
	int can; 
}a[2200][2200];

void B(int x,int y){
	for(int i=x-1,j=y-1;i>=1&&y>=1;i--,j--){
		if(s[i][j]!='.') break;
		a[i][j].can = 2;
	}
	for(int i=x+1,j=y+1;i<=n&&j<=m;i++,j++){
		if(s[i][j]!='.') break;
		a[i][j].can = 2;
	}
	for(int i=x+1,j=y-1;i<=n&&j>=1;i++,j--){
		if(s[i][j]!='.') break;
		a[i][j].can = 2;
	}
	for(int i=x-1,j=y+1;i>=1&&j<=m;i--,j++){
		if(s[i][j]!='.') break;
		a[i][j].can = 2;
	}
}
void K(int x,int y){
	int kx[8] = {-1,-1,-1,0,1,1,1,0};
	int ky[8] = {-1,0,1,1,1,0,-1,-1};
	for(int i=0;i<8;i++){
		if(a[x+kx[i]][y+ky[i]].can==0) a[x+kx[i]][y+ky[i]].can = 2;
	}
}
void R(int x,int y){
	for(int i=y+1;i<=m;i++){
		if(s[x][i]!='.') break;
		a[x][i].can = 2;
	}
	for(int i=y-1;i>=1;i--){
		if(s[x][i]!='.') break;
		a[x][i].can = 2;
	}
	for(int i=x+1;i<=n;i++){
		if(s[i][y]!='.') break;
		a[i][y].can = 2;
	}
	for(int i=x-1;i>=1;i--){
		if(s[i][y]!='.') break;
		a[i][y].can = 2;
	}
}
void P(int x,int y){
	if(a[x-1][y-1].can==0&&s[x-1][y-1]=='.') a[x-1][y-1].can = 2;
	if(a[x-1][y+1].can==0&&s[x-1][y+1]=='.') a[x-1][y+1].can = 2;
}
void Q(int x,int y){
	R(x , y);
	B(x , y);
}
void N(int x,int y){
	int nx[8] = {-1,-2,-2,-1,1,2,2,1};
	int ny[8] = {-2,-1,1,2,2,1,-1,-2};
	for(int i=0;i<8;i++){
		if(x+nx[i]<1||x+nx[i]>n||y+ny[i]<1||y+ny[i]>m) continue;
		if(s[x+nx[i]][y+ny[i]]!='.') continue;
		a[x+nx[i]][y+ny[i]].can = 2;
	}
}

void add(int x,int y){//链式前向星建边
	to[++id] = y;
	ne[id] = head[x];
	head[x] = id;
}
void init(){//标记r,c数组
	int lenr=1;
	for(int i=1;i<=n+m-1;i++){
		for(int j=1;j<=n+m-1;j++){
			if(mp[i][j]==-1) continue;
			if(!mp[i][j]) r[i][j] = lenr;
			else if(mp[i][j]==1) lenr++;
		}
		lenr++;
	}
	maxx = lenr;
	
	int lenc=1;
	for(int i=1;i<=n+m-1;i++){
		for(int j=1;j<=n+m-1;j++){
			if(mp[j][i]==-1) continue;
			if(!mp[j][i]) c[j][i] = lenc;
			else if(mp[j][i]==1) lenc++;
		}
		lenc++;
	}
	
	for(int i=1;i<=n+m-1;i++){
		for(int j=1;j<=n+m-1;j++){
			if(!mp[i][j]) add(r[i][j],c[i][j]);
		}
	}
}

bool dfs(int u){//匈牙利算法
	for(int i=head[u];i;i=ne[i]){
		int v = to[i];
		if(vis[v]) continue;
		vis[v] = 1;
		if(!d[v] || dfs(d[v])){
			d[v] = u;
			return true;
		}
	}
	return false;
}
int main(){
	scanf("%d %d",&n,&m);
	for (int i=1;i<=n;i++){
		scanf("%s",s[i]+1);
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			node f = {i+j-1 , j+n-i , a[i][j].can};
			if(s[i][j]=='.'){
				a[i][j] = f;
				continue;
			}
			
			f.can = 1;
			a[i][j] = f;
			
			if(s[i][j]=='B'){
				B(i , j);
			}
			else if(s[i][j]=='K'){
				K(i , j);
				B(i , j);
			}
			else if(s[i][j]=='R'){
				R(i , j);
				B(i , j);
			}
			else if(s[i][j]=='P'){
				P(i , j);
				B(i , j);
			}
			else if(s[i][j]=='Q'){
				Q(i , j);
			}
			else if(s[i][j]=='N'){
				N(i , j);
				B(i , j);
			}
		}
	}
	memset(mp,-1,sizeof(mp));
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			mp[a[i][j].xx][a[i][j].yy] = a[i][j].can; 
		}
	}
		
	init();
		
	for(int i=1;i<maxx;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i)) ans++;
	}
	printf("%d\n",ans);
	
	return 0;
}

版权说明:本文为博主原创文章,转载请附上本文链接(https://blog.csdn.net/smart_CZH/article/details/125120137)。

2022-06-18 本文在洛谷用户caizehua的博客中发布。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值