《算法竞赛入门经典》 习题4-1(象棋 Xiangqi ACM ICPC Fuzhou 2011,UVa1589)

原题及翻译

Xiangqi is one of the most popular two-player board games in China.
象棋是中国最流行的两人棋类游戏之一。
The game represents a battle between two armies with the goal of capturing the enemy’s “general” piece.
这个游戏代表了两军之间的一场战斗,目的是干掉敌人的“将军”棋子。
In this problem, you are given a situation of later stage in the game.
在这个问题中,你会得到一个象棋残局。
Besides, the red side has already “delivered a check”.
另外,红方已经“将军”。
Your work is to check whether the situation is “checkmate”.
你的工作是检查情况是否是“将死”。
在这里插入图片描述
Now we introduce some basic rules of Xiangqi.
现在我们介绍一些象棋的基本规则。
Xiangqi is played on a 10×9 board and the pieces are placed on the intersections (points).
象棋在一块10×9的棋盘上演奏,棋子放在交叉点上。
The top left point is (1,1) and the bottom right point is (10,9).
左上点为(1,1),右下点为(10,9)。
There are two groups of pieces marked by black or red Chinese characters, belonging to the two players separately.
有两组黑体字或红体字的棋子,分别属于两人的棋子。
During the game, each player in turn moves one piece from the point it occupies to another point.
在游戏过程中,每个玩家依次将一个棋子从它占据的点移 动到另一个点。
No two pieces can occupy the same point at the same time.
任何两个棋子都不能同时占据同一个点。
A piece can be moved onto a point occupied by an enemy piece, in which case the enemy piece is“captured” and removed from the board.
一个棋子可以移动到一个被敌方棋子占据的点上,在这种情况下,敌方棋子被“吃掉”并从棋盘上移除。
When the general is in danger of being captured by the enemy player on the enemy player’s next move, the enemy player is said to have “delivered a check”.
当将军在敌方玩家下一步行动中有被敌方玩家吃掉的危险时,就说敌方玩家已经“将军”。
If the general’s player can make no move to prevent the general’s capture by next enemy move, the situation is called “checkmate”.
如果将军的玩家不能采取任何行动来阻止将军被下一个敌人的行动俘虏,这种情况就称为“将死”。
We only use 4 kinds of pieces introducing as follows:
我们只使用以下4种材料:
在这里插入图片描述
General: the generals can move and capture one point either vertically or horizontally and cannot leave the “palace” unless the situation called “flying general” (see the figure above). “Flying general” means that one general can “fly” across the board to capture the enemy general if they stand on the same line without intervening pieces.
将/帅:将/帅可以垂直或水平移动并吃掉敌方的棋子,除非被称为“飞行将/帅(老将对脸)”(见上图),否则不能离开“宫殿”。“飞行将/帅”是指如果一个将/帅站在同一条线上而中间又不隔着任何棋子的情况下,他就可以“飞”到敌方的将/帅处去吃掉敌方将/帅。
在这里插入图片描述
Chariot: the chariots can move and capture vertically and horizontally by any distance, but may not jump over intervening pieces
战车:战车可以任意距离垂直和水平移动和捕获,但不能跳过中间的棋子。
在这里插入图片描述
Cannon: the cannons move like the chariots, horizontally and vertically, but capture by jumping exactly one piece (whether it is friendly or enemy) over to its target.
加农炮:加农炮像战车一样水平和垂直移动,但通过跳到目标上(无论是友军还是敌人)来吃掉对方的棋子。
在这里插入图片描述
Horse: the horses have 8 kinds of jumps to move and capture shown in the left figure. However, if there is any pieces lying on a point away from the horse horizontally or vertically it cannot move or capture in that direction (see the figure below), which is called “hobbling the horse’s leg”.
马:如左图所示,马有8种跳跃动作和捕捉。然而,如果有任何棋子水平或垂直地躺在马的相邻点上,它就不能在该方向上移动(见下面的图),这被称为“马腿的蹒跚(蹩马腿)<真亏百度敢翻译成这样>”。
在这里插入图片描述
Now you are given a situation only containing a black general, a red general and several red chariots, cannons and horses, and the red side has delivered a check. Now it turns to black side’s move. Your job is to determine that whether this situation is “checkmate”.
现在你的处境只有一个将,一个帅和几辆红色战车,大炮和马匹,红色的一方已经将军了。现在轮到黑棋的走棋了。你的工作是确定这种情况是将否被“将死”。

Input

The input contains no more than 40 test cases. For each test case, the first line contains three integers representing the number of red pieces (2 n 7) and the position of the black general. The following N lines contain details of N red pieces. For each line, there are a char and two integers representing the type and position of the piece (type char ‘G’ for general, ‘R’ for chariot, ‘H’ for horse and ‘C’ for cannon). We guarantee that the situation is legal and the red side has delivered the check.
There is a blank line between two test cases. The input ends by ‘0 0 0’.
输入
输入包含的测试用例不超过40个。对于每一个测试用例,第一行包含三个整数,表示红色块数n(2≤n≤7)和黑色常规的位置。下面的n行包含n个红色棋子的详细信息。对于每一行,有一个字符和两个整数表示棋子的类型和位置(类型char ‘G’表示将/帅,R’表示战车,H’表示马,C’表示大炮)。我们保证这种情况是合法的,并且红方已经将军。两个测试用例之间有一个空白行。输入以“0 0 0”结尾。

Output

For each test case, if the situation is checkmate, output a single word ‘YES’, otherwise output the word ‘NO’.
Hint: In the first situation, the black general is checked by chariot and “flying general”. In the second situa- tion, the black general can move to (1, 4) or (1, 6) to stop check. See the figure on the right.
在这里插入图片描述
输出
对于每个测试用例,如果情况是“将死”,
则输出单个单词“yes”,否则输出单词“no”。
提示:
在第一种情况下,黑将军由战车和“飞将军”检查。
在第二种情况下,黑将军可以移动到
(1,4)或(1,6)停止检查。
请参见右侧的图。
Sample Input
样例输入
2 1 4
G 10 5
R 6 4
3 1 5
H 4 5
G 10 5
C 7 5
0 0 0
Sample Output
样例输出
YES NO

思路

1.象棋棋盘,首先想到建立一个二维数组,用来存储棋盘的每一个节点。
2.整体思路有两种(或者说我就能想到两种):
第一种:遍历所有的红棋棋子,标记出红方的攻击范围,然后找出黑棋将军的移动范围,判断其移动范围是否是红棋攻击范围的子集,如果是,则“将死”;
第二种:先找出黑棋将军所有的移动范围,判断每一个移动后可能遭到的红方棋子威胁,如果所有的移动方案都不可行,则“将死”。
3.分类讨论的思想,不管是那种方案,都需要分情况讨论。

完整代码

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int CheckerboardR[11][10];    //红棋盘
int CheckerboardB[11][10];   //黑棋盘
int red_number,Generalx,Generaly;
struct piece
{//声明棋子结构
 char type;
 int x;
 int y;
};
void General_scope(int x,int y)
{//黑棋将军的移动范围,利用20标识出来
     if(x+1<=3) CheckerboardB[x+1][y]=20;
  if(x-1>=1) CheckerboardB[x-1][y]=20;
  if(y+1<=6) CheckerboardB[x][y+1]=20;
  if(y-1>=4) CheckerboardB[x][y-1]=20;
}
int s_l_o_c_a_i_c(int x1,int y1,int x2,int y2)
{//判断两个棋子是否在同一行或同一列以及两个棋子之间是否有其它的棋子。
 if(x1==x2)
 {//如果在同一行。
     int flag=1;
  for(int i=x1+1;i<x2;i++)
  {//遍历从x1+1到x2的所以棋盘位置
            if(CheckerboardR[x1][i]!=0)
            {//如果有其它棋子
             flag=0;
   }
  }
  if(flag)
  {//如果两个棋子在同一行并且之间没有其它的棋子,返回10。
   return 10;
  }
  else
  {//如果两个棋子在同一行并且之间有其它的棋子,返回11。
   return 11;
  }
 }
 else if(y1==y2)
 {//如果在同一列。
     int flag=1;
  for(int i=y1+1;i<y2;i++)
  {//遍历从y1+1到y2的所以棋盘位置
            if(CheckerboardR[i][y1]!=0)
            {//如果有其它棋子
             flag=0;
   }
  }
  if(flag)
  {//如果两个棋子在同一列并且之间没有其它的棋子,返回20。
   return 20;
  }
  else
  {//如果两个棋子在同一列并且之间有其它的棋子,返回21。
   return 21;
  }
 }
 else
 {//如果既不在同一行,也不在同一列,返回0.
  return 0;
 }
}
void p_t_c_p_a_j_t_s_o_d(char type,int x,int y)
{//将输入的棋子放到棋盘上,并标记出该棋子统治的范围
 switch (type)
 {
  case 'G':
    {//如果红棋是帅
    for(int i=x+1;i>=1;i--)
    {
       if(CheckerboardR[i][y]==1||CheckerboardR[i][y]==2)
      break;
      else if(CheckerboardR[i][y]==0||CheckerboardR[i][y]==10)
      CheckerboardR[i][y]=10;
    }
       }
  case 'R':
    {//如果红棋是车
    for(int i=x-1;i>=1;i--)
    {
       if(CheckerboardR[i][y]==1||CheckerboardR[i][y]==2)
      break;
      else if(CheckerboardR[i][y]==0||CheckerboardR[i][y]==10)
      CheckerboardR[i][y]=10;
    }
    for(int i=x+1;i<=10;i++)
    {
       if(CheckerboardR[i][y]==1||CheckerboardR[i][y]==2)
      break;
      else if(CheckerboardR[i][y]==0||CheckerboardR[i][y]==10)
      CheckerboardR[i][y]=10;
    }
    for(int i=y+1;i<=9;i++)
    {
       if(CheckerboardR[i][y]==1||CheckerboardR[i][y]==2)
      break;
      else if(CheckerboardR[i][y]==0||CheckerboardR[i][y]==10)
      CheckerboardR[x][i]=10;
    }
     for(int i=y-1;i>=1;i--)
    {
       if(CheckerboardR[i][y]==1||CheckerboardR[i][y]==2)
      break;
      else if(CheckerboardR[i][y]==0||CheckerboardR[i][y]==10)
      CheckerboardR[x][i]=10;
    }
    }
  case 'H':
   {//如果红棋是马
           if(x+1==0||x+1==10)
     {
              CheckerboardR[x+2][y-1]=10;
                CheckerboardR[x+2][y+1]=10;
        }
        if(x-1==0||x-1==10)
       {
                   CheckerboardR[x-2][y-1]=10;
                      CheckerboardR[x+2][y+1]=10;
       }
                 if(y-1==0||y-1==10)
       {
             CheckerboardR[x+1][y-2]=10;
                          CheckerboardR[x-1][y-2]=10;
          }
           if(y+1==0||y+1==10)
       {
                          CheckerboardR[x+1][y+2]=10;
        CheckerboardR[x-1][y+2]=10;
         }
       }
  case 'C':
    {//如果红棋是炮
    if(s_l_o_c_a_i_c(x,y,Generalx,Generaly)==11||s_l_o_c_a_i_c(x,y,Generalx,Generaly)==21)
    {//判断炮与黑棋将军是否在同一条线上,以及中间是否有其它棋子
       for(int i=x-1;i>=1;i--)
     {
          int x1=0,y1=0;
       if(CheckerboardR[i][y]==1&&CheckerboardR[i][y]==2)
       {
                x1=i;y1=y;
            {
            for(int j=x1-1;i>=1;j--)
         {
             if(CheckerboardR[j][y]==1&&CheckerboardR[j][y]==2)
             break;
                 else
           CheckerboardR[j][y]=10;
         }
                 }
          }
       }
       }
          for(int i=y-1;i>=1;i--)
       {
               int x1=0,y1=0;
          if(CheckerboardR[x][i]==1&&CheckerboardR[x][i]==2)
          {
          x1=x;y1=i;
           for(int j=y1-1;i>=1;j--)
           {
               if(CheckerboardR[x][j]==1&&CheckerboardR[x][j]==2)
            break;
            else
            CheckerboardR[x][j]=10;
           }
              }
        }
       for(int i=y+1;i<=9;i++)
       {
                 int x1=0,y1=0;
            if(CheckerboardR[x][i]==1&&CheckerboardR[x][i]==2)
            {
              x1=x;y1=i;
         for(int j=y1+1;i<=9;j++)
         {
               if(CheckerboardR[x][j]==1&&CheckerboardR[x][j]==2)
           break;
           else
           CheckerboardR[x][j]=10;
         }
                }
       }
    }
    }
}
int main ()
{//主函数,用于输入数据和做最后的判断
      memset(CheckerboardR,0,sizeof(CheckerboardR));
   memset(CheckerboardB,0,sizeof(CheckerboardB));
  while(1)
  {
      cin>>red_number>>Generalx>>Generaly;
    //读入红方棋子是数和黑棋将军的坐标,这些坐标都作为全局变量声明
    CheckerboardB[Generalx][Generaly]=2;
    //在黑棋盘上将黑棋将军的坐标记录下来
    if(red_number==0||Generalx==0||Generaly==0) break;
    //用于结束输入
    struct piece red_piece[red_number];
    //声明一个存储红棋棋子结构的数组
    for(int i=1;i<=red_number;i++)
    {//将red_number行的红棋子数据读入
             cin>>red_piece[i].type;
       scanf("%d %d",&red_piece[i].x,&red_piece[i].y);
       //存储红棋棋子的类型和坐标
       CheckerboardR[red_piece[i].x][red_piece[i].y]=1;
       //在红棋棋盘上把相应的位置标出来
          }
    for(int i=1;i<=red_number;i++)
    {//利用函数将red_number行红棋的攻击范围标识出来
      p_t_c_p_a_j_t_s_o_d(red_piece[i].type,red_piece[i].x,red_piece[i].y);
       }
    General_scope(Generalx,Generaly);
    //调用函数将黑棋将军的移动范围标识出来
    //开始判断黑棋移动范围是否是红棋攻击范围的子集
    int checkmate1=0,checkmate2=0; //定义判断是否“将死”的变量
    for(int i=1;i<=3;i++)
    {
        for(int j=4;j<=6;j++)
      {
       /*经过一系列的函数处理之后,红棋的攻击范围与黑棋的移动范围被标识出来*/
       if(CheckerboardB[i][j]==20) checkmate1++;
       //如果遇到黑棋盘上标有20的位置,累加checkmate1
       if(CheckerboardB[i][j]==20&&CheckerboardR[i][j]==10) checkmate2++;
       //如果遇到黑棋盘上标有20,红棋盘上标有10的位置,累加checkmate2
         }
    }
    if(checkmate1==checkmate2) printf("YES \n");
    else printf("NO \n");
    memset(CheckerboardR,0,sizeof(CheckerboardR));
    memset(CheckerboardB,0,sizeof(CheckerboardB));
    //将两张棋盘重置,为下次输入做准备
    }
return 0;
}

结语

看来英语不好也是个大问题啊,不然连ACM的题目都看不懂。
还有,百度翻译,哎,无力吐槽。

每天磕一道ACM打卡