• UVA


    /*
      法一:
    	
      之前不会象棋的玩法,为了这题特意百度了许久,弄清楚了基本规则
      
      另,这题一开始的代码十分繁琐,后来看到了一篇很好的博客,他的思路就很简洁:
      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;
    }


  • 相关阅读:
    Apache Struts 2.3.12 GA?
    emacs配置《转》
    vim配置
    vim插件
    git使用
    ubuntu常用设置
    Eclipse如何关联已经clone的Git项目
    变量名、对象引用(指针)与堆栈
    Web项目转换为groovy项目的步骤
    日志 20071208(SvcUtil.exe,高并发网站架构)
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789515.html
Copyright © 2020-2023  润新知