洛谷_P1457 [USACO2.1]城堡 The Castle (尚贤)

【题目】:https://www.luogu.com.cn/problem/P1457

/*
这道题的前两个答案就是求连通块数量和最大连通块
后两个答案可以枚举每面墙,然后把拆了的墙的两边的房间的面积加起来,求最大值(所谓“有多解时选最靠西的”就是选j最小的,“仍然有多解时选最靠南的”就是选i最大的,) 
(“同一格子北边的墙比东边的墙更优先。”这个可以先把北墙先拆了,再拆东墙) 
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#define max(a, b) ((a > b) ? a : b)
#define SIZE 50 + 5
using namespace std;
struct node{
	bool fang[5];
	// 分别表示 北 南 西 东 方向是否有墙
	// ture = 有墙, false = 没墙 
	int number; // 这个格子所属的房间的大小 
	int belong; // 这个格子所属房间的编号 
	node(){}
	node(const bool &a, const bool &b, const bool &c, const bool &d) {
		fang[0] = a, fang[1] = b, fang[2] = c, fang[3] = d;
	}
};
struct node2{
	int number1, number2; // 联通块数量和最大的连通块 
	node2() {}
	node2(const int &x, const int &y) {
		number1 = x;
		number2 = y;
	}
};
struct node3{
	int x, y; // x y坐标
	node3() {}
	node3(const int &xx, const int &yy) {
		x = xx, y = yy;
	}
};
int walk[5][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 这是按照上北下南,左西右东的方向来写的 
int n, m;
node map[SIZE][SIZE];
bool vis[SIZE][SIZE];
bool pd(const int &x, const int &y) {return (x > 0 && y > 0 && x <= n && y <= m);}
node3 a[(int)1e5]; // BFS用的队列 

node2 count(); // 计算最大连通块及连通块数量 
void fun_1(); // 计算前两个答案 
int bfs(const int &x, const int &y, const int &z); // x y 代表BFS搜索的起始坐标,z表示当前搜的格子的房间编号 
node count2(const int &x); //  
void fun_2(); // 计算后两个答案 


int main() {
	freopen("cpp.in", "r", stdin);
	freopen("cpp.out", "w", stdout);
	scanf("%d%d", &m, &n);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			int x;
			scanf("%d", &x);
			map[i][j] = count2(x); // 读入并赋值 
		}
	}
	fun_1();
	fun_2();
	return 0;
}

void fun_1() {
	memset(vis, 0, sizeof(vis));
	node2 temp = count();
	printf("%d\n%d\n", temp.number1, temp.number2);
}

node2 count() { // 计算最大连通块及连通块数量 
	node2 ans = node2(0, 0);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (!vis[i][j]) { // 如果这间房没有被覆盖 
				++ans.number1;
				int temp = bfs(i, j, ans.number1);
				ans.number2 = max(temp, ans.number2);
			}
		}
	}
	return ans;
}
int bfs(const int &x, const int &y, const int &z) { // 标记并返回连通块大小 
	int begin, end;
	begin = end = vis[x][y] = 1; // 初始化 
	a[end++] = node3(x, y); // 把这个塞进队列 
	while (begin != end) {
		node3 now = a[begin++];
		for (int i = 0; i < 4; ++i) {
			int nx = now.x + walk[i][0];
			int ny = now.y + walk[i][1];
			if (pd(nx, ny) && !map[now.x][now.y].fang[i] && !vis[nx][ny]) { // 如果坐标合法,并且要走的方向没有墙和没走过 
				a[end++] = node3(nx, ny); // 塞进队列 
				vis[nx][ny] = true; // 标记为走过 
			}
		}
	}
	for (int i = 1; i < end; ++i) { //把曾经到过的每一项初始化 
		map[a[i].x][a[i].y].number = end - 1;
		map[a[i].x][a[i].y].belong = z;
	}
	return end - 1;
}
node count2(const int &x) { // 返回房间的墙的情况
	node ans = node(0, 0, 0, 0);
	/*
	首先,来分析问题,1,2,4,8,这些数显然是有特点的,也许你已经想到了没错,它们都是2的次方数。
	1是2的0次方	
	2是2的1次方	
	4是2的2次方	
	8是2的3次方	
	知道这个就好办了,用什么呢?没错是位运算,哈哈!	
	1的二进制是1	
	2的二进制是10	
	4的二进制是100	
	8的二进制是1000	
	于是,就得出了以下代码(注意,我这里的顺序是北南西东):
	*/
	if (x & 1) ans.fang[2] = true;
	if (x & 2) ans.fang[0] = true;
	if (x & 4) ans.fang[3] = true;
	if (x & 8) ans.fang[1] = true;
	return ans;
}
void fun_2() { // 计算后两个答案
	memset(vis, 0, sizeof(vis)); 
	int ans = -1, ii = 51, jj = -1;
	char ans_;
	// 最后输出的答案 
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (map[i][j].fang[0] && i > 1 && map[i][j].belong != map[i - 1][j].belong) { // 北面有没有墙 
				int temp =  map[i][j].number + map[i - 1][j].number; // 将墙拆了后,两房的面积加起来 
				if (temp == ans) { // 判断是否可以替换 
					if (jj == j) {
						if (i > ii) {
							ans = temp, ii = i, jj = j, ans_ = 'N';
						}
					} else if (j < jj) {
						ans = temp, ii = i, jj = j, ans_ = 'N';
					}
				} else if (temp > ans) {
					ans = temp, ii = i, jj = j, ans_ = 'N';
				}
			}
			if (map[i][j].fang[3] && j < m && map[i][j].belong != map[i][j + 1].belong) { // 东面有没有墙
				int temp = map[i][j].number + map[i][j + 1].number; // 将墙拆了后,两房的面积加起来 
				if (temp == ans) { // 判断是否可以替换 
					if (jj == j) {
						if (i > ii) {
							ans = temp, ii = i, jj = j, ans_ = 'E';
						}
					} else if (j < jj) {
						ans = temp, ii = i, jj = j, ans_ = 'E';
					}
				} else if (temp > ans) {
					ans = temp, ii = i, jj = j, ans_ = 'E';
				}
			}
		}
	}
	printf("%d\n%d %d %c\n", ans, ii, jj, ans_);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值