习题4-1 UVA1589 Xiangqi(60行AC代码)

紫书刷题进行中,题解系列点这里


习题4-1 UVA1589 Xiangqi

题目大意

给定[2,7]个红子,1个黑子将军,问黑子移动一步后,红子能否将军。

其中红子包括4类棋子:

  • 帅:红方格内水平/垂直移动一格或飞将(与将在同一列,且中间没有其它棋子阻隔)
  • 车:水平或垂直移动任意格
  • 炮:水平或垂直移动任意格,炮A若想吃棋子B,当且仅当二者之间仅有一个棋子
  • 马:走日字格8个方向,当存在马脚时对应两个方向无法跳转

思路分析

对于黑子将军,遍历四个可能的方向,在每个方向遍历所有红子的攻击,若存在某个方向所有红子均无法攻击到黑子将军,说明黑子将军不会被将,否则,会被将军。

  • 方向向量:为了便于模拟黑子将军的4个方向和马的8个方向,同时便于处理马脚问题(假设马遍历到第j个向量,那么j/2就是马脚向量),定义方向向量如下(注意二者对应关系)
int gdict[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; // 将军4个方向
int hdict[8][2] = {{-1,2},{1,2}, {1,-2},{-1,-2}, {2,1},{2,-1}, {-2,-1},{-2,1}}; // 马8个方向
  • 同时定义isCheck标记黑子是否会被将军;定义board[11][10],其中board[i][j]=false表示坐标(i,j)上不存在子。
bool isCheck = false, board[11][10] = {false}; // 标记点是否有子
  • 定义vector<Pos> red ,存储红子坐标及类型信息,便于后续遍历

帅,车,炮模拟的关键在于判断到达将军位置的过程中经过多少颗棋子,因此设置水平和垂直计算函数,其中,调用函数前需保证将军和相应棋子在同一水平/垂直线上;具体实现时还需考虑黑子吃红子情况,由于默认黑子将军不记录board,因此对返回值多一步处理:

  • 水平间隔棋子计数:
int hpos(int px, int py, int y) { // 水平方向:判断将军和当前棋子相差个数
    int ans = 0, l = min(py, y), r = max(py, y);
    for (int j = l; j <= r; j ++) if (board[px][j]) ans ++;
    return ans - board[px][py]; // 考虑吃红子情况
}
  • 垂直间隔棋子计数:
int vpos(int px, int py, int x) { // 垂直方向:判断将军和当前棋子相差个数
    int ans = 0, l = min(px, x), r = max(px, x);
    for (int i = l; i <= r; i ++) if (board[i][py]) ans ++;
    return ans - board[px][py]; // 考虑吃红子情况
}

注意点

  • 测试用例不存在开局黑子将军可飞将红子帅的情况
  • 黑子将军可以吃红子!!!
  • 注意马脚判断和落子位置有效性判断

AC代码(C++11,复杂模拟,象棋)

#include<bits/stdc++.h>
using namespace std;
int gdict[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; // 将军4个方向
int hdict[8][2] = {{-1,2},{1,2}, {1,-2},{-1,-2}, {2,1},{2,-1}, {-2,-1},{-2,1}}; // 马8个方向
bool isCheck = false, board[11][10] = {false}; // 标记点是否有子
typedef struct Pos {
    char type; // 类型
    int x, y; // 坐标
    Pos(char _type=0, int _x=0, int _y=0) :  type(_type), x(_x), y(_y) {} // 构造函数
}Pos;
bool isLegal(int x, int y, int isG) { // 判断位置是否合法
    if (isG) return (x >= 1 && x <= 3) && (y >=4 && y <= 6); // 将军,可以吃红子
    else return (x >= 1 && x <= 10) && (y >= 1 && y <= 9); // 普通棋子
}
int hpos(int px, int py, int y) { // 水平方向:判断将军和当前棋子相差个数
    int ans = 0, l = min(py, y), r = max(py, y);
    for (int j = l; j <= r; j ++) if (board[px][j]) ans ++;
    return ans - board[px][py]; // 考虑吃红子情况
}
int vpos(int px, int py, int x) { // 垂直方向:判断将军和当前棋子相差个数
    int ans = 0, l = min(px, x), r = max(px, x);
    for (int i = l; i <= r; i ++) if (board[i][py]) ans ++;
    return ans - board[px][py]; // 考虑吃红子情况
}
int main() {
    int n,  x, y, tx, ty;
    char type;
    while (scanf("%d %d %d", &n, &x, &y) == 3 && n != 0) {
        vector<Pos> red; // 存储红子坐标信息
        fill(board[0], board[0]+110, 0); // 棋盘初始化,仅记录是否有子
        while (n --) {
            cin >>type >>tx >>ty;
            board[tx][ty] = true; // 标记
            red.push_back(Pos{type, tx, ty}); // 存储
        }
        int px = x, py = y; // 将军移动可能达到的位置
        for (int i = 0; i < 4; i ++) { // 黑子将军4个方向
            px = x + gdict[i][0]; py = y + gdict[i][1]; // 黑方将军移动后的位置
            if (isLegal(px, py, 1)) { // 将军位置合法
                isCheck = false; // 初始化
                for (auto& p : red) { // 遍历所有红子
                    if (p.type == 'G' && py == p.y && vpos(px, py, p.x) == 1) isCheck = true; // 可杀死
                    else if (p.type == 'R' && (py == p.y && vpos(px, py, p.x) == 1 || px == p.x && hpos(px, py, p.y) == 1)) isCheck = true;
                    else if (p.type == 'C' && (py == p.y && vpos(px, py, p.x) == 2 || px == p.x && hpos(px, py, p.y) == 2)) isCheck = true;
                    else if (p.type == 'H') {
                        for (int j = 0; j < 8 && !isCheck; j ++) { // 马8个方向
                            int hx = p.x + hdict[j][0], hy = p.y + hdict[j][1]; // 日字格
                            int gx = p.x + gdict[j/2][0], gy = p.y + gdict[j/2][1]; // 马脚
                            if ((hx == px && hy == py) && isLegal(gx, gy, 0) && !board[gx][gy]) isCheck = true;
                        }
                    }
                    if (isCheck) break; // 一发现可以杀死立刻退出,进入下一个方向
                }
                if (!isCheck) break; // 可以不死,找到存活机会
            }
        }
        printf("%s\n", isCheck ? "YES" : "NO");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值