XMU上机部分试题详解

状态搜索:五子棋裁判

描述

在程序设计实践课上,老师给大家布置下了这样的实验任务:编写一个五子棋应用程序

胜负判定是五子棋程序最核心的部分。给出当前棋盘的状态,请你完成对五子棋胜负的判定(仅考虑当前状态,不考虑后续落子)

规则:在水平、垂直或对角线方向形成5个以上棋子连续的一方获胜

输入

输入第一行为一个整数 t (1≤t≤100),代表一共有 t 组输入

对于每组输入:

第一行一个整数 n (1≤n≤20),代表棋盘的大小为 n× n

之后的 n 行,每行包含一个长度为 n 的字符串,代表当前棋盘的状态,其中空位用字符.表示,黑棋、白棋分别用字符BW表示

输入保证只会出现胜负未定、黑棋胜利、白棋胜利三种情况(即最多只会有一方有5连子

输出

输出 t 行 ,分别为每组输入的胜负结果

若黑棋胜利输出"Black", 若白棋胜利输出"White",若胜负未定则输出"Not so fast"(均不包含引号)


思路

这个问题属于状态搜索问题,在搜索的过程中,我们需要检查棋盘上的状态,确定是否存在某一方已经完成了五子连珠。那么我们如何去搜索呢?因为本题题意比较明显,我们可以直接从棋盘的第一个点开始遍历搜索,对于每个遍历到的点直接往它的八个方向深入搜索即可。我们只需要在搜索到的棋子颜色相反或者碰壁(棋盘越界)时,判断此次搜索方向上有多少相同棋子在一条线上即可。因此我们还需要对每次某方向的搜索定义一个变量 cnt,用来记录有几个棋子连在一起。

因为当出现五子连珠(或 n 子连珠,n >= 5)的情况时,某一方就会获得胜利,因此当上述退出搜索条件触发后,我们要对 cnt 的大小进行判断,若 cnt >= 5,那么就表示此次搜索找到了五子连珠(或 n 子连珠,n >= 5)的情况,直接返回答案,否则就换一个方向搜索。


具体实现过程

经过上述分析,搜索的过程我们弄懂了,那么应该怎么进行具体的操作?注意到,题目要求输入的棋盘都由字符构成,因此我们可以定义一个存放 string 类型的 vector,用来接收输入的棋盘。

int n; //棋盘大小为n*n
cin >> n;
vector<string> board(n); //存放string的vector用于存放棋盘
for (int i = 0; i < n; ++i) {
    cin >> board[i];
}

然后,我们定义 checkWinner 函数,用来接收当前棋盘状态。接下来我们需要实现从棋盘起点开始往后搜索,对于每个点又要往八个方向搜索(上下左右与两条对角线)。第一个问题通过两层for 循环对棋盘进行遍历即可,第二个问题我们又应该如何实现?注意到,我们对棋盘遍历的方式是从 (0, 0) 开始往后,因此对于棋盘下部分的位置,我们可以不用再往回搜索判断有无五子连珠。具体一点,我们可以只用判断四个方向:水平向右、竖直向下、向左下、向右下。如果此处仍然不是很清楚,可以在草稿纸上模拟一下,会一目了然。

具体如何进行模拟,我们可以利用STL中的 pair 类型来存放移动方向,然后对四个方向进行遍历,记录连在一起的最多棋子数 cnt,最后判断 cnt 是否 >= 5即可。

string checkWinner(const vector<string>& board) {
    //定义pair来确定当前点移动的方向
    vector<pair<int, int>> directions = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 水平、垂直、对角线方向
    for (int i = 0; i < board.size(); ++i) {
        for (int j = 0; j < board[i].size(); ++j) {
            if (board[i][j] != '.') {
                for (const auto& dir : directions) {
                    int dx = dir.first, dy = dir.second;
                    int x = i, y = j;
                    int count = 0;

                    //一直往某一个方向移动,用循环判断该点是否出界,且该点是否与之前的点相同
                    while (x >= 0 && x < board.size() && y >= 0 && y < board[0].size() && board[x][y] == board[i][j]) {
                        ++count;
                        x += dx;
                        y += dy;
                    }

                    //若满足了五子连在一起,则输出答案
                    if (count >= 5) {
                        return (board[i][j] == 'B') ? "Black" : "White";
                    }
                }
            }
        }
    }
    return "Not so fast";
}

AC代码如下

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string checkWinner(const vector<string>& board) {
    //定义pair来确定当前点移动的方向
    vector<pair<int, int>> directions = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 水平、垂直、对角线方向
    for (int i = 0; i < board.size(); ++i) {
        for (int j = 0; j < board[i].size(); ++j) {
            if (board[i][j] != '.') {
                for (const auto& dir : directions) {
                    int dx = dir.first, dy = dir.second;
                    int x = i, y = j;
                    int count = 0;

                    //一直往某一个方向移动,用循环判断该点是否出界,且该点是否与之前的点相同
                    while (x >= 0 && x < board.size() && y >= 0 && y < board[0].size() && board[x][y] == board[i][j]) {
                        ++count;
                        x += dx;
                        y += dy;
                    }

                    //若满足了五子连在一起,则输出答案
                    if (count >= 5) {
                        return (board[i][j] == 'B') ? "Black" : "White";
                    }
                }
            }
        }
    }
    return "Not so fast";
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n; //棋盘大小为n*n
        cin >> n;
        vector<string> board(n); //存放string的vector用于存放棋盘
        for (int i = 0; i < n; ++i) {
            cin >> board[i];
        }
        cout << checkWinner(board) << endl;
    }
    //system("pause");
    return 0;
}

字符串处理与排序:混合比较Ex (Nerfed)

描述

给出若干互不相等的十进制、八进制、十六进制、二进制整数,请将它们排序并从小到大输出

注意输出时不需要保留整数在输入中的进制,只需要按正常十进制格式输出

其中不同进制给出的格式如下:

  • 十进制数:不包含前缀,例如1919810

  • 八进制数:前缀为单个0,例如01140514

  • 十六进制数:前缀为0x,字母为小写英文字母,例如0x1f1e330x66ccff

  • 二进制数:前缀为0b, 例如0b100b101010101

所有整数的数值部分均不包含前导零(即不会有0x00d0001这种输入的出现)

输入

输入为一行字符串,包含若干按题目描述格式给出的整数 a (1≤a≤10^8)

整数间以逗号隔开,保证整数互不相等,整数个数 n 满足1≤n≤100

输出

输出一行,将输入的整数排序并从小到大输出,各个数间以逗号隔开

不需要保留整数在输入中的进制,只需要按正常十进制格式输出


思路

考虑到本题有不同进制的数据输入,此时我们若仍然使用整形或长整型变量存储,在输入上可能会遇到不小的麻烦。因此我们可以统一使用 string 类型变量存储输入,后续处理再将字符串转为数字类型的变量。完成上述步骤后,再对得到数字进行排序即可。

可以看到,本题的思路并不复杂,应该属于比较直观的类型。本题的具体障碍点是我们如何将输入的字符串分割,并且将分割后的各部分转换成不同进制的数据类型。实际上,分割的操作并不需要我们去从头编写具体的代码,C++的STL为我们提供了相关函数,并存放在 sstream 类中。下面简要介绍C++的 sstream 类。

stringstream(sstream)

概述:<sstream>主要用来进行数据类型转换,由于<sstream>使用 string 对象来代替字符数组,就避免了缓冲区溢出、缓冲区遗留回车空格等问题。而且,因为传入参数和目标对象的类型会被程序自动推导出来,所以不存在错误的格式化符问题。

sstream 可以实现根据字符串中某个字符(如逗号、空格等)将字符串分割,看以下例子:

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

int main() {
    string input;
    cout << "Enter a string with comma-separated values: ";
    getline(cin, input); // 从标准输入读取一行字符串

    istringstream iss(input); // 创建一个字符串流

    vector<string> tokens; // 用于存储分割后的子串

    string token;
    while (getline(iss, token, ',')) { // 使用getline函数按逗号分割字符串
        tokens.push_back(token);
    }

    // 输出分割后的子串
    cout << "Tokens separated by comma:\n";
    for (const auto& t : tokens) {
        cout << t << endl;
    }

    return 0;
}

在上述代码中,首先创建了一个 sstream 字符串流 iss,然后定义一个 string 变量token,一个存放string 的向量tokens。通过向getline函数传入 iss 和 token以及逗号,可以得到字符串 token 按逗号分隔后的每个字符串,并将其存放在向量中。

再回到我们的问题,我们同样可以用以上的方法将字符串进行分割,再分别处理。对于之后的 string 转换成不同进制的数据变量,我们可以调用 stoi 函数,感兴趣的读者可以查阅相关使用方法,笔者在这里不再赘述。


AC代码

#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;

// 去除整数的前缀,并将其转换为十进制数
int convertToDecimal(const string& num) {
    if (num.find("0x") != string::npos) { //找到了16进制标志
        return stoi(num.substr(2), nullptr, 16);
    } else if (num.find("0b") != string::npos) { //找到了2进制标志
        return stoi(num.substr(2), nullptr, 2);
    } else if (num[0] == '0') { //8进制标志
        return stoi(num, nullptr, 8);
    } else { //10进制
        return stoi(num);
    }
}

int main() {
    string input;
    getline(cin, input);

    stringstream ss(input);
    string token;
    vector<int> numbers;

    // 将输入按逗号分隔并转换为十进制整数存储在数组中
    while (getline(ss, token, ',')) {
        numbers.push_back(convertToDecimal(token));
    }

    // 对整数数组进行排序
    sort(numbers.begin(), numbers.end());

    // 输出排序后的整数
    for (int i = 0; i < numbers.size(); ++i) {
        cout << numbers[i];
        if (i != numbers.size() - 1) {
            cout << ",";
        }
    }

    //system("pause");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YUKIPEDIA~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值