UVA

来源:互联网 发布:解压 mac 编辑:程序博客网 时间:2024/05/17 21:50
/*  法一:  之前不会象棋的玩法,为了这题特意百度了许久,弄清楚了基本规则    另,这题一开始的代码十分繁琐,后来看到了一篇很好的博客,他的思路就很简洁:  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; //typewhile (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;}