本文涉及知识点
P7555 [USACO21OPEN] Maze Tac Toe S
题目描述
奶牛 Bessie 喜欢玩走迷宫。她同样喜欢玩井字棋(更准确地说,奶牛版的井字棋,马上会进行介绍)。Farmer John 找到了一种全新的方式,可以使她同时玩到这两种游戏!
首先,奶牛井字棋:与在 3 × 3 3 \times 3 3×3 棋盘内放置 X
和 O
不同,奶牛当然是在 3 × 3 3 \times 3 3×3 棋盘内放置 M
和 O
。在一方的回合内,玩家可以选择将一个 M
或一个 O
放在任意一个空格内(这是另一个与标准井字棋的不同之处,标准的井字棋中一名玩家始终放 X
而另一名玩家始终放 O
)。奶牛井字棋的胜利方是首位拼出 MOO
的玩家,可以是同行、同列或对角线。倒着拼出也是可以的,例如一名玩家在棋盘的一行内拼出 OOM
也可以获胜。如同标准井字棋一样,有可能最后没有玩家取得胜利。奶牛井字棋的一次行动通常用 3 个字符表示,Mij
或 Oij
,其中 i i i 和 j j j 在范围 1 ... 3 1 \ldots 3 1...3 之间,表示放置 M
或 O
的行与列。
为了给 Bessie 增加一些挑战,Farmer John 设计了一个由 N × N N \times N N×N 个方格组成的正方形迷宫( 3 ≤ N ≤ 25 3 \leq N \leq 25 3≤N≤25)。某些方格,包括所有的边界方格,有巨大的草堆,使得 Bessie 不能移动到这些方格。Bessie 可以沿东南西北四个方向在迷宫内的所有其他方格内自由行动。某些方格内有一张纸,上面写有奶牛井字棋的一次行动。当 Bessie 在迷宫中移动时,任何时刻她停留在这样的方格上,她都必须于迷宫中移动之时在同时进行的奶牛井字棋游戏内做出对应的行动(除非奶牛井字棋中对应的方格已经被填上了,在这种情况下她不进行行动)。在奶牛井字棋游戏内没有与她对阵的玩家,但迷宫中的某些方格可能会不利于她最终拼出 MOO
。
如果 Bessie 在获胜之时就会立刻停止奶牛井字棋,请求出她可以通过适当的方式在迷宫中移动而完成的不同的胜利状态棋盘数量。
输入格式
输入的第一行包含 N N N。
以下 N N N 行,每行包含 3 N 3N 3N 个字符,描述迷宫。每个方格用 3 个字符表示,###
代表墙,...
代表空格,BBB
代表 Bessie 所在的一个非墙方格,或者一个奶牛井字棋的行动,表示 Bessie 必须进行对应行动的一个非墙方格。恰好一个方格为 BBB
。
输出格式
输出 Bessie 可以通过在迷宫中行动并在获胜时立刻停止而产生的不同的胜利状态下的奶牛井字棋盘数量(可能为 0 0 0)。
输入输出样例 #1
输入 #1
7
#####################
###O11###...###M13###
###......O22......###
###...######M22######
###BBB###M31###M11###
###...O32...M33O31###
#####################
输出 #1
8
说明/提示
样例说明
在这个样例中,Bessie 最终可能达成 8 8 8 种胜利的棋盘状态:
plain
O.M
.O.
MOM
O..
.O.
.OM
O.M
.O.
.OM
O..
.O.
MOM
O..
...
OOM
..M
.O.
OOM
...
.O.
OOM
...
...
OOM
对其中一种情况进行说明:
plain
O..
...
OOM
在这里,Bessie 可以先移动到 O11
方格,然后移动到下方并通过 O32
、M33
和 O31
。此时游戏结束,因为她获得了胜利(例如她不能再前往位于她当前所在的 O31
方格北面的 M11
方格)。
说明
供题:Brian Dean
BFS
状态:当前位置(行列),棋盘样式。 前者数量:N*N,后者数量 3 9 3^9 39。总数量: 5 × 1 0 5 5\times 10^5 5×105。
临接矩阵:4临接 且 不是墙。
状态转移:位置一定发生变化,棋盘可能发生变化。当前状态是胜利状态,则无后续状态。
初始状态:枚举各非墙单格,并执行此单格的事件。
出重:vis数组。
最终结果:枚举所有胜利棋盘状态,vis[棋盘状态][任意行][任意列]为真,则ans+。
代码
核心代码
cpp
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n;
cin >> n;
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
vector<T> ret;
T tmp;
while (cin >> tmp) {
ret.emplace_back(tmp);
if ('\n' == cin.get()) { break; }
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<int N = 1'000'000>
class COutBuff
{
public:
COutBuff() {
m_p = puffer;
}
template<class T>
void write(T x) {
int num[28], sp = 0;
if (x < 0)
*m_p++ = '-', x = -x;
if (!x)
*m_p++ = 48;
while (x)
num[++sp] = x % 10, x /= 10;
while (sp)
*m_p++ = num[sp--] + 48;
AuotToFile();
}
void writestr(const char* sz) {
strcpy(m_p, sz);
m_p += strlen(sz);
AuotToFile();
}
inline void write(char ch)
{
*m_p++ = ch;
AuotToFile();
}
inline void ToFile() {
fwrite(puffer, 1, m_p - puffer, stdout);
m_p = puffer;
}
~COutBuff() {
ToFile();
}
private:
inline void AuotToFile() {
if (m_p - puffer > N - 100) {
ToFile();
}
}
char puffer[N], * m_p;
};
template<int N = 1'000'000>
class CInBuff
{
public:
inline CInBuff() {}
inline CInBuff<N>& operator>>(char& ch) {
FileToBuf();
ch = *S++;
return *this;
}
inline CInBuff<N>& operator>>(int& val) {
FileToBuf();
int x(0), f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
inline CInBuff& operator>>(long long& val) {
FileToBuf();
long long x(0); int f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
val = f ? -x : x; S++;//忽略空格换行
return *this;
}
template<class T1, class T2>
inline CInBuff& operator>>(pair<T1, T2>& val) {
*this >> val.first >> val.second;
return *this;
}
template<class T1, class T2, class T3>
inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
return *this;
}
template<class T1, class T2, class T3, class T4>
inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
return *this;
}
template<class T = int>
inline CInBuff& operator>>(vector<T>& val) {
int n;
*this >> n;
val.resize(n);
for (int i = 0; i < n; i++) {
*this >> val[i];
}
return *this;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
*this >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read() {
vector<T> ret;
*this >> ret;
return ret;
}
private:
inline void FileToBuf() {
const int canRead = m_iWritePos - (S - buffer);
if (canRead >= 100) { return; }
if (m_bFinish) { return; }
for (int i = 0; i < canRead; i++)
{
buffer[i] = S[i];//memcpy出错
}
m_iWritePos = canRead;
buffer[m_iWritePos] = 0;
S = buffer;
int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
if (readCnt <= 0) { m_bFinish = true; return; }
m_iWritePos += readCnt;
buffer[m_iWritePos] = 0;
S = buffer;
}
int m_iWritePos = 0; bool m_bFinish = false;
char buffer[N + 10], * S = buffer;
};
class CGrid {
public:
CGrid(int rCount, int cCount) :m_r(rCount), m_c(cCount) {}
template<class Fun1, class Fun2>
vector<vector<pair<int, int>>> NeiBo(Fun1 funVilidCur, Fun2 funVilidNext, int iConnect = 4)
{
vector<vector<pair<int, int>>> vNeiBo(m_r * m_c);
for (int r = 0; r < m_r; r++)
{
for (int c = 0; c < m_c; c++)
{
if (!funVilidCur(r, c))continue;
auto& v = vNeiBo[Mask(r, c)];
if ((r > 0) && funVilidNext(r - 1, c)) {
v.emplace_back(r - 1, c);
}
if ((c > 0) && funVilidNext(r, c - 1)) {
v.emplace_back(r, c - 1);
}
if ((r + 1 < m_r) && funVilidNext(r + 1, c)) {
v.emplace_back(r + 1, c);
}
if ((c + 1 < m_c) && funVilidNext(r, c + 1)) {
v.emplace_back(r, c + 1);
}
}
}
return vNeiBo;
}
vector<vector<int>> Dis(vector<pair<int, int>> start, std::function<bool(int, int)> funVilidCur, std::function<bool(int, int)> funVilidNext, const int& iConnect = 4) {
static short dir[8][2] = { {0, 1}, {1, 0}, {-1, 0},{ 0, -1},{1,1},{1,-1},{-1,1},{-1,-1} };
vector<vector<int>> vDis(m_r, vector<int>(m_c, -1));
for (const auto& [r, c] : start) {
vDis[r][c] = 0;
}
for (int i = 0; i < start.size(); i++) {
const auto [r, c] = start[i];
if (!funVilidCur(r, c)) { continue; }
for (int k = 0; k < iConnect; k++) {
const int r1 = r + dir[k][0];
const int c1 = c + dir[k][1];
if ((r1 < 0) || (r1 >= m_r) || (c1 < 0) || (c1 >= m_c)) { continue; }
if (funVilidNext(r1, c1) && (-1 == vDis[r1][c1])) {
start.emplace_back(r1, c1);
vDis[r1][c1] = vDis[r][c] + 1;
}
}
}
return vDis;
}
void EnumGrid(std::function<void(int, int)> call)
{
for (int r = 0; r < m_r; r++)
{
for (int c = 0; c < m_c; c++)
{
call(r, c);
}
}
}
vector<pair<int, int>> GetPos(std::function<bool(int, int)> fun) {
vector<pair<int, int>> ret;
for (int r = 0; r < m_r; r++)
{
for (int c = 0; c < m_c; c++)
{
if (fun(r, c)) {
ret.emplace_back(r, c);
}
}
}
return ret;
}
inline int Mask(int r, int c) { return m_c * r + c; }
const int m_r, m_c;
const inline static int s_Moves[][2] = { {1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1} };
};
class Solution {
public:
int Ans(vector<string>& mat) {
const int N = mat.size();
int MMC = 1;
for (int i = 0; i < 9; i++) { MMC *= 3; }
const int MC = MMC * N * N;
CGrid grid(N, N);
auto Vilid = [&](int r, int c) {
return "###" != mat[r].substr(3 * c, 3);
};
auto neiBo = grid.NeiBo(Vilid, Vilid);
vector<int> suc(MMC);
vector<array<array<int, 3>, 3>> maskToMaze(MMC);
{
for (int mask = 0; mask < MMC; mask++)
{
auto& maze = maskToMaze[mask];
auto tmp = mask;
for (int r = 2; r >= 0; r--)for (int c = 2; c >= 0; c--) {
maze[r][c] = tmp % 3;
tmp /= 3;
}
suc[mask] = IsSuc(maze);
}
}
vector<bool> vis(MC);
queue<tuple<int, int, int>> que;
auto Add = [&](int MMask, int r, int c) {
const int m = N * N * MMask + N * r + c;
if (vis[m]) { return; }
vis[m] = true;
que.emplace(MMask, r, c);
if (suc[MMask]) { suc[MMask] = 2; }
};
auto Do = [&](int PreMMask, int r, int c) {
string str = mat[r].substr(3 * c, 3);
if ("###" == str) { return; }
else if ("..." == str) { Add(PreMMask, r, c); }
else if ("BBB" == str) { Add(PreMMask, r, c); }
else {
const int mr = str[1] - '1';
const int mc = str[2] - '1';
int MMask = PreMMask;
if (0 == maskToMaze[PreMMask][mr][mc]) {
int add = ('M' == str[0]) ? 2 : 1;
MMask += Unit(mr, mc) * add;
}
Add(MMask, r, c);
}
};
for (int r = 0; r < N; r++)for (int c = 0; c < N; c++) {
string str = mat[r].substr(3 * c, 3);
if ("BBB" != str) { continue; }
Do(0, r, c);
}
while (que.size()) {
const auto [MM, r, c] = que.front(); que.pop();
if (suc[MM] > 1) { continue; }
for (const auto& [r1, c1] : neiBo[N * r + c]) {
Do(MM, r1, c1);
}
}
return count(suc.begin(), suc.end(), 2);
}
bool IsSuc(int x, int y, int z) { return IsSuc1(x, y, z) || IsSuc1(z, y, x); }
bool IsSuc1(int x, int y, int z) {
return (2 == x) && (1 == y) && (1 == z);
}
bool IsSuc(array<array<int, 3>, 3>& maze) {
if (IsSuc(maze[0][0], maze[1][1], maze[2][2])) { return true; }//对角线
if (IsSuc(maze[0][2], maze[1][1], maze[2][0])) { return true; }//反对角线
for (int rc = 0; rc < 3; rc++) {
if (IsSuc(maze[rc][0], maze[rc][1], maze[rc][2])) { return true; }
if (IsSuc(maze[0][rc], maze[1][rc], maze[2][rc])) { return true; }
}
return false;
}
int Unit(int r, int c) {
int ret = 1;
while (r < 2) { r++; ret *= 27; }
while (c < 2) { c++, ret *= 3; }
return ret;
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::sync_with_stdio(0); cin.tie(nullptr);
//CInBuff<> in; COutBuff<10'000'000> ob;
auto mat = Read<string>();
#ifdef _DEBUG
//printf("M=%d,a=%d,", M,a);
Out(mat, "mat=");
//Out(B, "B=");
//Out(que, "que=");
//Out(B, "B=");
#endif // DEBUG
auto res = Solution().Ans(mat);
cout << res << "\n";
//ob.write(res); ob.write('\n');
return 0;
}
单元测试
cpp
int N;
vector<string> mat;
TEST_METHOD(TestMethod1)
{
mat = { "#####################","###O11###...###M13###","###......O22......###","###...######M22######","###BBB###M31###M11###",
"###...O32...M33O31###","#####################" };
auto res = Solution().Ans( mat);
AssertEx(8, res);
}
扩展阅读
我想对大家说的话 |
---|
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。