/* 法一: 之前不会象棋的玩法,为了这题特意百度了许久,弄清楚了基本规则 另,这题一开始的代码十分繁琐,后来看到了一篇很好的博客,他的思路就很简洁: http://blog.csdn.net/hao_zong_yin/article/details/53780164 于是,我也回去修改了自己的代码,才有了这个最终的版本 另外总结一下,上面那篇博客究竟好到哪里,为什么能简化代码? 因为,它其实是作了一个替换,我们事先无法知道红子的个数,需要等n去输入,而我们却清楚地知道,黑子只有一个... 这样的情况下,我们先模拟完黑子走的那步(如果当时是"飞将"的情况,黑子走完这步就能胜利,直接结束游戏,不出现checkmate)以后,其实可以有两种思路: 1.枚举黑将可走的位置,判断是否有不会被红子下轮攻击到的位置,没有就被将死 2.找到所有红子可将军的所有可能位置,再找黑将可走的位置中,有没有能幸免于难的,如果一个都没有,就被将死了 总体来说,我觉得1会更加简单 但是现在的问题就是,真的要一步步去模拟所有红子的位置吗? 上面的链接的博主就转换了思路,把红子移动仍然当成是黑子移动(运动是相对的,黑子下移就表示了红子上移),黑子向整个棋盘(注意!!此时我们是用黑子的移动代替红子,黑子不是只能在九宫里走了,而是和红子的移动范围一样,是整个棋盘,这里尤其注意!!!)4个方向移动,找是否存在能够将死它的红子,这样就只用枚举4个方向,会简单许多 因为,如果想要找到红子的所有攻击区域,每个红子都要枚举方向,还要考虑不同的攻击规则 因此,不如就把红子的移动用黑子的代替了,去找有没有能将死它的红子即可 突然觉得,上面的哪个博主的思路真厉害,用了转换以后,感觉代码也简单许多了。也感受到,同一道题目,用不同的角度,简直就是有新的发现,有一种豁然开朗的感觉...看来每道题都值得挖深一点,果然做完题还该时刻反省:这是不是我能想到的最好的做法了?还可以有别的思路吗? BTW,注释是写给和我一样对象棋十分小白的人,以防我下次复习这道题时,又忘了象棋的规则 T^T...熟悉规则的就,让你见笑了... */
#include <iostream> #include <cstring> #include <string> using namespace std; char chessboard[20][20]; int IsCheckmate(int x, int y); int IsAlive(int x, int y); int main() { int n, x, y, x1, y1; char t; //type while (cin >> n >> x >> y && n && x && y) { memset(chessboard, 0, sizeof(chessboard)); //清空上一组数据的记录 for (int i = 0; i < n; i++) { cin >> t >> x1 >> y1; chessboard[x1][y1] = t; //放子 } if (IsCheckmate(x, y)) cout << "YES" << endl; else cout << "NO" << endl; } return 0; } int IsCheckmate(int x, int y) { int i; char ch; for (i = x; i <= 10; i++) { if (chessboard[i][y] != 0) break; } if (i <= 10 && chessboard[i][y] == 'G') return 0; //初始情况下,将帅如果碰面,且满足"飞将"规则,帅死,不会出现 checkmate 的局面 //枚举四个方向,对每个方向,先判断移动是否越界(将能走的界限是九宫,3 * 3的一个区域),确认不越界后,如果黑将向某个方向移动,能保证在下步中可不被将死,则返回0,表示黑将不会checkmate,否则恢复原来的棋盘(类似“回溯法”的思想),继续尝试别的方向,循环往复,直到这个某个方向不被将死(返回0),或方向枚举完毕(返回1) if (x - 1 >= 1) { ch = chessboard[x - 1][y]; chessboard[x - 1][y] = 0; if ( IsAlive(x - 1, y) ) return 0; else chessboard[x - 1][y] = ch; } /* 解释下这个if分支的意义,下面的几个if同理: 1.if内的内容保证不越界,之所以要赋值ch,是因为黑子上移的位置,可能是空白(直接落子),也可能是红子(被黑子吃掉),不管怎么说,最后chessboard的相应位置都要清空, 因为chessboard里面记录的,都是红子的位置。黑子上移,将它的位置作为参数传给IsAlive(),判断是否有不被将死的可能 2.如果上移后,在红方移动后,无处可避,只能尝试别的方向,此时注意要先把 对应位置 恢复为ch,这里和回溯法的思想十分类似 */ if (x + 1 <= 3) { ch = chessboard[x + 1][y]; chessboard[x + 1][y] = 0; if ( IsAlive(x + 1, y) ) return 0; else chessboard[x + 1][y] = ch; } if (y - 1 >= 4) { ch = chessboard[x][y - 1]; chessboard[x][y - 1] = 0; if ( IsAlive(x, y - 1)) return 0; else chessboard[x][y - 1] = ch; } if (y + 1 <= 6) { ch = chessboard[x][y + 1]; chessboard[x][y + 1] = 0; if ( IsAlive(x, y + 1)) return 0; else chessboard[x][y + 1] = ch; } return 1; } int IsAlive(int x, int y) //判断黑方如果放到(x,y)以后,红方走子后,黑方是否有逃脱不被将死的可能 { //判断是否会被周围的马吃掉,此处几个if,是为了判断:1.是否在马的攻击范围内,2.是否是蹩马腿,3.是否越界 //注意此时马的攻击范围的圈定,是红马在中心,黑将在8个可能的攻击点,再由黑将的坐标,推出可能导致自己被将死的红马位置,以及为了保证不发生蹩马腿,需要不为红子的那个位置(之前我就弄反过...看来我转换的思想还没学到位) //几个 if 说明此时黑将在红马的攻击范围内,且不会被蹩马腿,则这种走法会被红马攻击,不可取,回溯,下面几种情况同理,但有时还要增加对越界的判断(这是出于对黑将初始状态的考虑,结合移动的范围,可能向上出界,但不会向下向左向右出界) if (chessboard[x + 2][y - 1] == 'H' && chessboard[x + 1][y - 1] == 0) return 0; if (chessboard[x + 2][y + 1] == 'H' && chessboard[x + 1][y + 1] == 0) return 0; if (chessboard[x + 1][y - 2] == 'H' && chessboard[x + 1][y - 1] == 0) return 0; if (chessboard[x + 1][y + 2] == 'H' && chessboard[x + 1][y + 1] == 0) return 0; if (x - 2 >= 1 && chessboard[x - 2][y - 1] == 'H' && chessboard[x - 1][y - 1] == 0) return 0; if (x - 2 >= 1 && chessboard[x - 2][y + 1] == 'H' && chessboard[x - 1][y + 1] == 0) return 0; if (x - 1 >= 1 && chessboard[x - 1][y - 2] == 'H' && chessboard[x - 1][y - 1] == 0) return 0; if (x - 1 >= 1 && chessboard[x - 1][y + 2] == 'H' && chessboard[x - 1][y + 1] == 0) return 0; int i, num = 0; // 现在开始遍历四个方向,看看黑将是否在红方的任意车、炮、将的攻击范围 // num用来记录,目前这个红子是该方向遇到的第几个红子,注意num在每次换方向时,都要清零 // 注意车和炮类似,除了炮在吃子时,需要和被吃的子恰好相隔一子,而车若想自由移动而不被挡住,需要红车与黑将之间,没有其他红方棋子的阻碍 // 注意4个方向里,有个比较特殊的就是,向下找有没有棋子能攻击黑将时,要考虑"飞将"这种可能 // num++,以及满足特定条件时return 0的判断,仅在 有红子且 num == 1/2时,是因为,如果已经遇到了两个红子还没被吃,那就绝对不会再被吃子了,因为车和炮的吃子方式,都不是隔着两个子还能吃子的 for (i = x; i >= 1; i--) { if (chessboard[i][y] != 0) { num++; if (num == 1 && chessboard[i][y] == 'R') return 0; else if (num == 2 && chessboard[i][y] == 'C') return 0; else if (num == 2) break; } } // 上方没有能攻击它的红子 num = 0; for (i = x; i <= 10; i++) { if (chessboard[i][y] != 0) { num++; if (num == 1 && ( chessboard[i][y] == 'R' || chessboard[i][y] == 'G' )) return 0; //注意:向下可飞将 else if (num == 2 && chessboard[i][y] == 'C') return 0; else if (num == 2) break; } } num = 0; for (i = y; i >= 1; i--) { if (chessboard[x][i] != 0) { num++; if (num == 1 && chessboard[x][i] == 'R') return 0; else if (num == 2 && chessboard[x][i] == 'C') return 0; else if (num == 2) break; } } num = 0; for (i = y; i <= 9; i++) { if (chessboard[x][i] != 0) { num++; if (num == 1 && chessboard[x][i] == 'R') return 0; else if (num == 2 && chessboard[x][i] == 'C') return 0; else if (num == 2) break; } } return 1; }
/* 法二:这种方法是先找到红子的所有可攻击范围,再找黑子可走的坐标中,有没有不在红子攻击范围内的位置 参考了这个blog的代码,我发现这个博主写的十分简洁,于是借用了他的思路和设计了哪些函数,对我自己写的长代码做了一些改进,以下贴出自己改进后的AC代码 blog: http://blog.csdn.net/shihongliang1993/article/details/73287616 (BTW,我对题意的理解,似乎和上面的这个博主不太一样,不过最终都能AC,那应该就没什么关系了) 题意:黑方只剩下一个将,红方还有很多子,而且红方已出子,轮到黑方出子,判断是否出现checkmate (凡走子直接攻击对方将帅者谓之“将军”(check),其中另一方无法解围的情况称为“将死”(checkmate)) 换句话说,就是判断黑将走了一子以后,红方再将军时,黑子能否解围,不能则被将死 1.找出红方能吃到的位置,记录到check数组中 2.给棋子编码,炮是1,马是2,车是3,因为在对将的时候帅的作用和车是一样的,因此把帅也编成3号,按照车处理(帅吃将其实只有一种可能,就是帅是将正下方遇到的第一个红子) 3.这中间使用两个棋盘来记录,board是残局时每个棋子的位置,check记录判断后红方能够吃到的位置,0代表不能吃到,-1表示能吃到。 若只用一个棋盘,如果一个红子被另外一个红子所吃,它被标记为别的,而非1/2/3,在后面的判断时,就会漏掉这个红子的攻击范围的标记,最终导致出错 4.边界的判断易错,因为黑将移动时可能吃掉红子 因此在判断红方棋子能吃到的位置的时候,要考虑到红方能够吃到的红方自己的第一个棋子(这是为了未雨绸缪,如果这第一个棋子被黑子吃掉了,黑子就恰在红方攻击范围内了) 值得一提的是,这个博主的代码中,十分巧妙的一点是,他用了几个数组,来实现黑子的走子,红马的走子(马的可走位置和马腿<马腿是对应"蹩马腿"的叫法,表示某个位置被挡以后,马不能攻击对应方向的棋子>的位置,它们一一对应时的坐标变化量,都是通过数组实现的),简化了代码 此外,坐标的合法性判断也是通过函数实现的,也精简了代码,值得学习 */
#include <iostream> #include <cstring> using namespace std; int board[20][20], check[20][20]; int dxy[5][2] = {{-1, 0}, {1, 0}, {0, 0}, {0, - 1}, {0, 1}}; int attack[8][2] = { {-1, 2}, {1, 2}, {-1, -2}, {1, -2}, {-2, -1}, {-2, 1}, {2, 1}, {2, -1} }; int leg[8][2] = { {0, 1}, {0, 1}, {0, -1}, {0, -1}, {-1, 0}, {-1, 0}, {1, 0}, {1, 0} }; int islegal(int x, int y) { return (x >= 1 && x <= 10 && y >= 1 && y <= 9); } void chessH(int x, int y) // horse { for (int i = 0; i < 8; i++) { int lx = x + leg[i][0], ly = y + leg[i][1]; int hx = x + attack[i][0], hy = y + attack[i][1]; if (islegal(lx, ly) && board[lx][ly] == 0 && islegal(hx, hy)) check[hx][hy] = -1; } } void chessR(int x, int y) //chariot { for (int i = x - 1; i >= 1; i--) { if (islegal(i, y)) { check[i][y] = -1; if (board[i][y] != 0) break; } } for (int i = x + 1; i <= 10; i++) { if (islegal(i, y)) { check[i][y] = -1; if (board[i][y] != 0) break; } } for (int i = y - 1; i >= 1; i--) { if (islegal(x, i)) { check[x][i] = -1; if (board[x][i] != 0) break; } } for (int i = y + 1; i <= 9; i++ ) { if (islegal(x, i)) { check[x][i] = -1; if (board[x][i] != 0) break; } } } void chessC(int x, int y)// cannon { int t; t = x - 1; while (islegal(t, y) && board[t][y] == 0) t--; for (int i = t - 1; i >= 1; i--) { if (islegal(i, y)) { check[i][y] = -1; if (board[i][y] != 0) break; } } t = x + 1; while (islegal(t, y) && board[t][y] == 0) t++; for (int i = t + 1; i <= 10; i++) { if (islegal(i, y)) { check[i][y] = -1; if (board[i][y] != 0) break; } } t = y - 1; while (islegal(x, t) && board[x][t] == 0) t--; for (int i = t - 1; i >= 1; i--) { if (islegal(x, i)) { check[x][i] = -1; if (board[x][i] != 0) break; } } for (int i = t + 1; i <= 9; i++) { if (islegal(x, i)) { check[x][i] = -1; if (board[x][i] != 0) break; } } } int main() { int n, x1, y1, x, y; char t; while (cin >> n >> x1 >> y1 && n && x1 && y1) { memset(board, 0, sizeof(board)); memset(check, 0, sizeof(check)); for (int i = 0; i < n; i++) { cin >> t >> x >> y; switch(t) { case 'C': board[x][y] = 1; break; case 'H': board[x][y] = 2; break; case 'R': board[x][y] = 3; case 'G': board[x][y] = 3; break; } } for (x = 1; x <= 10; x++) for (y = 1; y <= 9; y++) { if (board[x][y] > 0) switch(board[x][y]) { case 1: chessC(x, y);break; case 2: chessH(x, y);break; case 3: chessR(x, y);break; } } int flag = 1; for (int i = 0; i < 5; i++) { x = x1 + dxy[i][0]; y = y1 + dxy[i][1]; if (x >= 1 && x <= 3 && y >= 4 && y <= 6 && check[x][y] == 0) { flag = 0; break; } } cout << (flag ? "YES" : "NO") << endl; } return 0; }