目录
题目背景
本题 Python (pypy3) 与 Java (javac) 的时限为其他编译器的两倍。使用 Python3 语言的选手请提交到 Python (pypy3) 中。
Khoray 是一个围棋天才,他正在参加 CCCC-GPLT (Chongqing Collegiate Chess Championship - Grandmaster Pro League Tournament,重庆大学生围棋冠军杯-大师传奇锦标赛),比赛的部分规则如下:
围棋棋盘是一个有限大小的二维平面,定义围棋棋盘的坐标 (x,y) 为从上往下第 x 个横线与从左往右第 y 个竖线的交叉点处。定义棋盘上的两个坐标相邻为这两个坐标的曼哈顿距离为 1 即两个坐标满足:∣x1−x2∣+∣y1−y2∣=1。
定义棋盘上的两个棋子连通为两个棋子同色且下列两个条件至少满足一个:
- 两个棋子坐标相邻。
- 记两个棋子为 a,b,存在一个棋子 c 使得 a,c 连通,且 b,c 连通。
定义棋盘上的一个连通块 S 为一个极大的棋子集合,使得集合中要么只有一个棋子,要么任意两个棋子都连通。
定义连通块 S 的气的集合 QS 为棋盘上的一个空位构成的极大集合,且对于任意 x∈QS 都存在一个 y∈S 使得 x,y 的坐标相邻。我们称这个连通块的气数为 ∣QS∣。如图,三角形标注的即为黑色连通块的气的集合。
当己方落子时,对于一个空位置 p,按照以下规则依次进行判定:
-
若落子于 p 后,对手棋子颜色的某个连通块的气数为 0,则允许在此处落子,且该连通块被提走(即将这些棋子拿走并放入己方的棋盒盖,棋盘上的相应位置变为空位)。如图,黑色落子于三角形标注的位置后,白色的连通块都被提走。
-
若落子于 p 后,无法使 1 成立,且己方棋子颜色的某个连通块的气数为 0,则该位置为禁着点,不允许落子在此处。例如下图中,叉标记位置为禁着点。
-
若落子于 p 后,无法使 1 2 成立,则允许在此处落子,且不会有提子环节。
现在 Khoray 正在一个 n×n 的棋盘上与对手较量,他执黑棋。比赛正进行到最关键的时刻,当他落子时,他看了一眼自己的棋盒盖发现已经放入了很多棋子,很快就要溢出了。他为了不因棋盒盖溢出而导致判负,你需要帮助他计算当前局面下对于棋盘上的每一个位置,若他落子于这个位置,会提走对方的多少个棋子。若这个位置不是空位或是禁着点,则输出 −1。
输入格式:
第一行包含一个正整数 n (1≤n≤1000)---表示棋盘大小。
接下来的 n 行中,每一行都是一个长度为 n 的字符串,且仅由 XO.
三种字符组成。第 i 行的第 j 个字符表示棋盘上坐标为 (i,j) 的位置,其中 X
表示这个位置有一个黑棋,O
表示白棋,.
表示这是一个空位。
保证输入的棋盘中,所有连通块气数不为 0。
输出格式:
输出 n 行,每行 n 个整数,第 i 行的第 j 个整数表示若落子于棋盘上坐标为 (i,j) 的位置,会提走对手多少个子。特别的,若这个位置不是空位或是禁着点,则输出 −1。
输入样例 1:
6
O.O.OO
.OX.XO
OXOOXX
OXXXX.
OOOOXX
.O.OX.
输出样例 1:
-1 -1 -1 3 -1 -1
-1 -1 -1 2 -1 -1
-1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 0
-1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 0
样例 1 的情况如图所示:
输入样例 2:
5
X.X.O
OOO.X
O..OX
.O..O
XOXOO
输出样例 2:
-1 0 -1 1 -1
-1 -1 -1 0 -1
-1 0 0 -1 -1
-1 -1 0 3 -1
-1 -1 -1 -1 -1
样例 2 的情况如图所示:
代码长度限制
16 KB
Python (pypy3)
时间限制
2000 ms
内存限制
512 MB
Java (javac)
时间限制
2000 ms
内存限制
512 MB
其他编译器
时间限制
1000 ms
内存限制
512 MB
栈限制
8192 KB
思路
围棋规则不再赘述,有问题可以自行查阅相关资料。
本质上就是一个图遍历问题,深度优先搜索和广度优先搜索都可以,蒟蒻广度优先写起来更加顺手,遂选择了广度优先。
分析题目,显然当一个坐标不为‘.’的时候,直接输出-1即可。
当一个坐标为‘.’时,我们需要判断是否为禁着点。
禁着点判断
首先,当放下黑棋后,能够提起白棋,那么一定不是禁着点。
然后,如果四周有任一空位,那么一定不是禁着点。
最后,如果四周连有黑棋,且此黑棋所在的连通块有两点及以上的气,那么此时一定不是禁着点。
除了以上情况,均是禁着点。
提子数量判断
如果不是禁着点,我们需要计算提子数量。
思路也很简单,如果四周连有白棋,且此白棋所在的连通块有且仅有一点气,那么此白棋与其联通的所有白棋均被提起。
优化
以上所有思路明确,但是为了降低时间复杂度,我们先统计每个白棋剩余气、联通数、连通块编号(重要!!),再统计每个黑棋的剩余气,最后再遍历棋盘,依次判断即可。
另外,如果直接使用数组(data[][])来存储,可能会出现栈溢出的情况,所以必须使用vector来存储。。。注意vector的初始化方法,不可以使用memset来初始化。
注意
有些连通块同时使用了一个空坐标,但是此时只能统计一次气。可以使用set进行管理。
有些提子情况,多个方向是同一个白棋连通块,但是此时也只能统计一次。可以使用连通块编号进行管理。
然后就可以了~
完整代码
#include <iostream>
#include <string>
#include <set>
#include <queue>
using namespace std;
int main() {
int n;
cin >> n;
vector<string> s(n);
vector<vector<int>> bqq(n, vector<int>(n, -1));
vector<vector<int>> bqkh(n, vector<int>(n, -1));
vector<vector<bool>> visit(n, vector<bool>(n, false));
vector<vector<int>> hqq(n, vector<int>(n, -1));
vector<vector<int>> lts(n, vector<int>(n, -1));
for (int i = 0; i < n; i++) {
cin >> s[i];
}
int kh = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (s[i][j] == 'O' && bqq[i][j] == -1) { // 白棋
set<pair<int, int>> ltzb;
set<pair<int, int>> qizb;
queue<pair<int, int>> q;
q.push({i, j});
while (!q.empty()) {
int nowh = q.front().first;
int nowl = q.front().second;
q.pop();
if (!visit[nowh][nowl]) {
visit[nowh][nowl] = true;
ltzb.insert({nowh, nowl});
// 统计联通数
if (nowh - 1 >= 0 && !visit[nowh - 1][nowl] && s[nowh - 1][nowl] == 'O') {
q.push({nowh - 1, nowl});
}
if (nowl - 1 >= 0 && !visit[nowh][nowl - 1] && s[nowh][nowl - 1] == 'O') {
q.push({nowh, nowl - 1});
}
if (nowh + 1 < n && !visit[nowh + 1][nowl] && s[nowh + 1][nowl] == 'O') {
q.push({nowh + 1, nowl});
}
if (nowl + 1 < n && !visit[nowh][nowl + 1] && s[nowh][nowl + 1] == 'O') {
q.push({nowh, nowl + 1});
}
// 统计气数
if (nowh - 1 >= 0 && s[nowh - 1][nowl] == '.') {
qizb.insert({nowh - 1, nowl});
}
if (nowl - 1 >= 0 && s[nowh][nowl - 1] == '.') {
qizb.insert({nowh, nowl - 1});
}
if (nowh + 1 < n && s[nowh + 1][nowl] == '.') {
qizb.insert({nowh + 1, nowl});
}
if (nowl + 1 < n && s[nowh][nowl + 1] == '.') {
qizb.insert({nowh, nowl + 1});
}
}
}
for (auto itor : ltzb) {
bqq[itor.first][itor.second] = qizb.size();
lts[itor.first][itor.second] = ltzb.size();
bqkh[itor.first][itor.second] = kh;
}
kh++;
} else if (s[i][j] == 'X' && hqq[i][j] == -1) { // 黑棋
int qi = 0;
set<pair<int, int>> ltzb;
set<pair<int, int>> qizb;
queue<pair<int, int>> q;
q.push({i, j});
while (!q.empty()) {
int nowh = q.front().first;
int nowl = q.front().second;
q.pop();
if (!visit[nowh][nowl]) {
visit[nowh][nowl] = true;
ltzb.insert({nowh, nowl});
if (nowh - 1 >= 0 && !visit[nowh - 1][nowl] && s[nowh - 1][nowl] == 'X') {
q.push({nowh - 1, nowl});
}
if (nowl - 1 >= 0 && !visit[nowh][nowl - 1] && s[nowh][nowl - 1] == 'X') {
q.push({nowh, nowl - 1});
}
if (nowh + 1 < n && !visit[nowh + 1][nowl] && s[nowh + 1][nowl] == 'X') {
q.push({nowh + 1, nowl});
}
if (nowl + 1 < n && !visit[nowh][nowl + 1] && s[nowh][nowl + 1] == 'X') {
q.push({nowh, nowl + 1});
}
// 统计气数
if (nowh - 1 >= 0 && s[nowh - 1][nowl] == '.') {
qizb.insert({nowh - 1, nowl});
}
if (nowl - 1 >= 0 && s[nowh][nowl - 1] == '.') {
qizb.insert({nowh, nowl - 1});
}
if (nowh + 1 < n && s[nowh + 1][nowl] == '.') {
qizb.insert({nowh + 1, nowl});
}
if (nowl + 1 < n && s[nowh][nowl + 1] == '.') {
qizb.insert({nowh, nowl + 1});
}
}
}
for (auto itor : ltzb) {
hqq[itor.first][itor.second] = qizb.size();
}
}
}
}
bool bigf = false;
for (int i = 0; i < n; i++) {
if (bigf) {
cout << endl;
}
bigf = true;
bool flag = false;
for (int j = 0; j < n; j++) {
if (s[i][j] != '.') {
if (flag)
cout << ' ';
flag = true;
cout << "-1";
} else {
bool can = false;
int ti = 0;
// 白棋
set<int> bkh;
if (i - 1 >= 0 && s[i - 1][j] == 'O' && bqq[i - 1][j] == 1 && bkh.find(bqkh[i - 1][j]) == bkh.end()) {
ti += lts[i - 1][j];
bkh.insert(bqkh[i - 1][j]);
}
if (j - 1 >= 0 && s[i][j - 1] == 'O' && bqq[i][j - 1] == 1 && bkh.find(bqkh[i][j - 1]) == bkh.end()) {
ti += lts[i][j - 1];
bkh.insert(bqkh[i][j - 1]);
}
if (i + 1 < n && s[i + 1][j] == 'O' && bqq[i + 1][j] == 1 && bkh.find(bqkh[i + 1][j]) == bkh.end()) {
ti += lts[i + 1][j];
bkh.insert(bqkh[i + 1][j]);
}
if (j + 1 < n && s[i][j + 1] == 'O' && bqq[i][j + 1] == 1 && bkh.find(bqkh[i][j + 1]) == bkh.end()) {
ti += lts[i][j + 1];
bkh.insert(bqkh[i][j + 1]);
}
// 黑棋
if (i - 1 >= 0 && s[i - 1][j] == 'X' && hqq[i - 1][j] > 1) {
can = true;
}
if (j - 1 >= 0 && s[i][j - 1] == 'X' && hqq[i][j - 1] > 1) {
can = true;
}
if (i + 1 < n && s[i + 1][j] == 'X' && hqq[i + 1][j] > 1) {
can = true;
}
if (j + 1 < n && s[i][j + 1] == 'X' && hqq[i][j + 1] > 1) {
can = true;
}
// 空位
if (i - 1 >= 0 && s[i - 1][j] == '.') {
can = true;
}
if (j - 1 >= 0 && s[i][j - 1] == '.') {
can = true;
}
if (i + 1 < n && s[i + 1][j] == '.') {
can = true;
}
if (j + 1 < n && s[i][j + 1] == '.') {
can = true;
}
if (ti) {
if (flag)
cout << ' ';
flag = true;
cout << ti;
} else {
if (can) {
if (flag)
cout << ' ';
flag = true;
cout << "0";
} else {
if (flag)
cout << ' ';
flag = true;
cout << "-1";
}
}
}
}
}
}
其实感觉可以优化代码,比如把方向进行存储,然后用for来遍历方向,可以简化代码。但是不太会,,做出来就行了,优化就留给后人吧~