紫书刷题进行中,题解系列点这里
习题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;
}