UVA1589 象棋
象棋是中国最受欢迎的两人桌游之一。该游戏代表了两支军队之间的战斗,目的是捕获敌人的“将军”。
现在我们介绍一些象棋的基本规则。
象棋在10×9的棋盘上演奏,并将棋子放置在交叉点(点)上。左上角是(1,1),右下角是(10,9)。有两组用黑色或红色汉字标记的棋子,分别属于两个玩家。在游戏中,每个玩家依次将一块从其占据的位置移动到另一点。没有两个片段可以同时占据同一点。可以将一个棋子移动到一个敌方棋子占据的点上,在这种情况下,该敌棋子被“捕获”并从棋盘上移走。
当某一方的将军有被敌方玩家下一步行动俘虏的危险时,就称敌方玩家已经 “交了支票” (处于优势地位)。如果该将军玩家没有采取任何行动来阻止敌方玩家下一步的占领,则这种情况称为 “将死”。
我们仅使用以下四种介绍:
将军:将军可以垂直或水平移动并捕获一个点,除非“飞行将军”的情况(参见上图),否则不能离开“宫殿”。 “飞行将军”意味着:如果它和对方的将军位于同一直线上,且这条连线上没有其他棋子,则它就可以把对方的将军捕获。
车:可以水平或垂直方向移动任意个无阻碍的点,即,想要移动到的位置必须和所处位置之间没有隔着其他棋子。
炮:炮可以像车一样在水平和垂直方向上移动,但它必须跳过一个棋子来吃掉对方的一个棋子。
马:马有8种跳跃动作来移动和捕获。但是,如果有任何棋子在水平或垂直方向上都偏离马的点,则无法沿该方向移动或捕获(请参见下图),这被称为“憋马腿”。
现在的情况是:只有一个黑人将军,以及一个红色将军和几个红色战车、大炮和马匹,且红方已发出支票(占据优势)。现在轮到黑方了。您的工作是确定这种情况是否会输棋。
输入
输入输入包含不超过40个测试用例。对于每个测试用例,第一行包含三个整数,分别代表红色碎片N的数量(2≤N≤7)和黑色将军的位置。接下来的N行包含N个红色片段的详细信息。每行都有一个char和两个整数,分别代表该棋子的类型和位置(char字母“ G”代表将军,“ R”代表战车,“ H”代表马,“ C”代表大炮)。我们保证情况是合法的,并且红方已交付了支票。在两个测试用例之间有一个空白行。输入以“ 0 0 0”结尾。
输出
对于每个测试用例,如果情况已死,则输出一个单词“YES”,否则输出单词“NO”。
题目大意概述
其实上面可以不用太理解,直接看这里吧:
红方目前处于优势地位,下一步是黑方出旗,我们的目的是判断这关键的一步——黑方的出旗是导致自己输掉,还是不会输掉。如果发生输棋,则输出“YES”,否则,输出“NO”。其实有两种情况会导致“输棋”:自己的将被对方的将“将死”,或,自己的将被对方的某个棋子吃掉。继续往下看,就彻底理解这两种情况了。
思路
第一步就是要进行黑将的移动,判断出可行的移动方式;然后,再进一步判断做出的这一可行移动是否会发生“将死”情况。
1
找可行的移动方式是很容易的,只需要判断移动到的位置的是否在“九宫”内:
如果向上走,则判断 b_row - 1 > 0 否?
如果向下走,则判断 b_row + 1 < 4 否?
如果向左走,则判断 b_column - 1 > 3 否?
如果向右走,则判断 b_column + 1 < 7 否?
2
找到一个可行的移动方式后,就需要判断黑将移动到这里后的局面是否“将死”。这里就需要我们非常理解题目上介绍的那四种规则了。就是利用上面的四种规则进行判断是否“将死”。下面再一一按照写代码的思路做出阐述。
黑将移动到某个位置后,这个位置和红将所在位置位于同一列,且中间没有隔有其他棋子,则“将死”。
车能够在棋盘上的任何位置上进行移动,所以要分别进行行、列的判断:
- 黑将移动到某个位置后,这个位置和红车在同一列,且中间没有隔有其他棋子,则红车可以吃掉黑将。
- 黑将移动到某个位置后,这个位置和红车在同一行,且中间没有隔有其他棋子,则红车可以吃掉黑将。
- 炮能够在棋盘上的任何位置上进行移动,所以要分别进行行、列的判断
- 黑将移动到某个位置后,这个位置和红炮在同列,且中间只隔了一个棋子,红炮可以将黑将吃掉。
- 黑将移动到某个位置后,这个位置和红炮在同行,且中间只隔了一个棋子,红炮可以将黑将吃掉。
- 判断黑将移动到的这个位置是否是“蹩马腿”的局面指的是:图中对号或叉号旁的点都是马不可以到达的地方,但前提是马的上/下/左/右没有棋子。这样的话,下一步红方就能让马吃掉黑棋,最终导致黑棋输。(马的移动方向——水平或垂直移动一点,然后再沿对角线向左或者向右移动一点。)
代码需要做的是:先判断红马的上下左右点上是否有障碍物,如果其中某一方位没有障碍物,则判断黑将新到达的位置是否是沿左对角线或右对角线的位置,如果是的话,则黑棋输,因为下一步红方就能让马吃掉黑棋。
(似乎不管是“将死”还是“被吃掉”,都可以直接成为“将死”…下面也不再进行区分)
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
//黑将的四个移动方向(X2[i], Y2[i])
int X2[] = {1,0,0,-1}; //X表示行,所以-1表示向上
int Y2[] = {0,1,-1,0}; //Y表示列,所以-1表示向左
vector<char> cate(10); //红方棋子的类别
vector<pair<int,int>> pos(10); //红方棋子的位置
//黑将移动到的位置与所在索引为i的位置的红棋位于同一行或同一列的情况下,计算间隔的棋子数。
vector<pair<int,int>> mid(int x1, int y1, int x2, int y2)
{
vector<pair<int,int>> result;
if(x1==x2) //如果这两个棋子位于同一行
{
if(y1>y2)
{
for(int k = 0;k < cate.size();k ++)
{
if(pos[k].first==x1&&pos[k].second<y1&&pos[k].second>y2)
result.push_back({pos[k].first,pos[k].second});
}
}
else if(y1<y2)
{
for(int k = 0;k < cate.size();k ++)
{
if(pos[k].first==x1&&pos[k].second>y1&&pos[k].second<y2)
result.push_back({pos[k].first,pos[k].second});
}
}
}
else if(y1==y2) //如果这两个棋子位于同一列
{
if(x1>x2)
{
for(int k = 0;k < cate.size();k ++)
{
if(pos[k].second==y1&&pos[k].first>x2&&pos[k].first<x1)
result.push_back({pos[k].first,pos[k].second});
}
}
else if(x1<x2)
{
for(int k = 0;k < cate.size();k ++)
{
if(pos[k].second==y1&&pos[k].first<x2&&pos[k].first>x1)
result.push_back({pos[k].first,pos[k].second});
}
}
}
return result;
}
bool inChessBoard(int x,int y) //是否在棋盘上
{
return x>0 && y>0 && x<=10 && y<=9;
}
bool inPalace(int x,int y) //是否在九宫内
{
return y>=4 && y<=6 && x<=3;
}
bool blackGSurvive(int BGx, int BGy)
{
int i;
for(int i = 0; i < 4; i++) //黑将的四个移动方向
{
int newX = BGx + X2[i];
int newY = BGy + Y2[i];
if(inChessBoard(newX,newY) && inPalace(newX,newY))
{
int j;
for(j = 0; j < cate.size(); j++) //遍历红方棋子
{
if(cate[j]=='G')
{
if(pos[j].second == newY) //位于同一列
{
vector<pair<int,int>> tm = mid(pos[j].first,pos[j].second,newX,newY);
if(tm.size()==0) //输
break;
}
}
else if(cate[j]=='R')
{
if(pos[j].first==newX) //位于同一行
{
if(pos[j].second==newY)
{
continue;
}
vector<pair<int,int>> tm = mid(pos[j].first,pos[j].second,newX,newY);
if(tm.size()==0) //输
break;
}
else if(pos[j].second==newY) //位于同一列
{
vector<pair<int,int>> tm = mid(pos[j].first,pos[j].second,newX,newY);
if(tm.size()==0) //输
break;
}
}
else if(cate[j]=='C')
{
if(pos[j].first==newX) //位于同一行
{
vector<pair<int,int>> tm = mid(pos[j].first,pos[j].second,newX,newY);
if(tm.size()==1&&tm[0].second!=newY
|| tm.size()==2&&(tm[0].second==newY||tm[1].second==newY)) //输
break;
}
else if(pos[j].second==newY) //位于同一列
{
vector<pair<int,int>> tm = mid(pos[j].first,pos[j].second,newX,newY);
if(tm.size()==1&&tm[0].first!=newX
|| tm.size()==2&&(tm[0].first==newX||tm[1].first==newX)) //输
break;
}
}
else if(cate[j]=='H')
{
int shang = 1,zuo = 1,you = 1,xia = 1;//判断马的上下左右是否已有棋子,初始化为1表示没有棋子
for(int k = 0; k < cate.size(); k ++)
{
if(j!=k && pos[k].second==pos[j].second && pos[k].first==pos[j].first-1)
shang = 0;
if(j!=k && pos[k].second==pos[j].second && pos[k].first==pos[j].first+1)
xia = 0;
if(j!=k && pos[k].second==pos[j].second-1 && pos[k].first==pos[j].first)
zuo = 0;
if(j!=k && pos[k].second==pos[j].second+1 && pos[k].first==pos[j].first)
you = 0;
}
//在上/下/左/右没有棋子的情况下,继续判断黑将是否移动到了马可以跳跃到的地方
if(shang)
{
/*
这里if语句里是黑将不可行的位置,
所以判断黑将新的位置是否真的是在这个不可行的位置上,即判断是否相等,
如果相等的话,则表明黑将移到了不可行的位置上,所以黑将输。
(这个位置之所以是输的位置,是因为黑将移动到这里后,
下一步红方就可以让自己的马吃掉这个黑将,
所以黑将最终的结果是输。)
下同。
*/
if(pos[j].first-2==newX && pos[j].second-1==newY
|| pos[j].first-2==newX && pos[j].second+1==newY)
break;
}
if(xia)
{
if(pos[j].first+2==newX&&pos[j].second-1==newY
|| pos[j].first+2==newX&&pos[j].second+1==newY)
break;
}
if(zuo)
{
if(pos[j].first-1==newX&&pos[j].second-2==newY
|| pos[j].first+1==newX&&pos[j].second-2==newY)
break;
}
if(you)
{
if(pos[j].first-1==newX&&pos[j].second+2==newY
|| pos[j].first+1==newX&&pos[j].second+2==newY)
break;
}
}
}
/*
上面跳出循环的原因都是因为黑将输了,
所以一旦最终j == cate.size(),则说明没有跳出过循环,
说明黑将一直没有输.
*/
if(j == cate.size())
{
return true;
}
}
}
return false;
}
int main()
{
int N, BGx, BGy;
while(cin >> N >> BGx >> BGy)
{
if(N == BGx && N == BGy && N == 0)
break;
while(N--)
{
char type; //象棋的类别(将/马/车/炮)
int Rx, Ry;
cin >> type >> Rx >> Ry;
cate.push_back(type);
pos.push_back({Rx,Ry});
}
if(blackGSurvive(BGx, BGy))
printf("NO\n");
else
printf("YES\n");
cate.clear();
pos.clear();
}
return 0;
}
UVA 220 黑白棋
一共有八个方向去遍历,你还想真的写八份代码啊???当然不是!又要进行函数的抽取了!
- 把【找黑/白玩家的可行棋子】抽成一个函数
- 把【判断是否有可行棋子】、【打印可行棋子】抽成一个函数(因为都需要遍历可行棋子数组)
- 把黑白棋表示为 1 和 0
AC代码
#include <stdio.h>
#include <iostream>
#include <vector>
#include <math.h>
#include <algorithm>
#include <queue>
#include <string.h>
#include <set>
#include <stack>
#include <stdlib.h>
#include <time.h>
using namespace std;
char mp[10][10]; //储存棋局
int f[2][10][10];//标记黑白子可执行合法操作的点(0为白棋,1为黑棋)
//dx、dy共同决定一个移动的方向
int dx[] = {-1,-1,-1, 1,1,1,0, 0}; //表示第i行,所以-1表示向上
int dy[] = {-1, 0, 1,-1,0,1,1,-1}; //表示第j列,所以-1表示向左
/*
求出位于(x, y)位置的a类型棋子的可行位置;b类型是a类型的对手。
求可行位置的思路是:
判断a类型棋子的上下左右及斜对角线上是否有b类型的棋子
如果有的话,再继续沿相同的方向走进行判断
直至遇到空白位或遇到了a类型棋子
如果是前者,则这个位置是一个合法操作,打印出来
如果是后者,则这个位置不可行
*/
void is(int a, char b, int x, int y)//
{
for(int k=0; k<8; k++)
{
int tx = x+dx[k];
int ty = y+dy[k];
int num = 0;
while(tx>=0 && ty>=0 && tx<8 && ty<8 && mp[tx][ty] == b)
{
tx += dx[k];
ty += dy[k];
++num;
}
if(num>=1 && mp[tx][ty] == '-')
f[a][tx][ty] |= 1<<k; //利用位运算同时保存k,即可行的移动方向,便于后面落子时直接定向翻转。
}
}
void init() //获得黑白子可执行操作的点
{
for(int i=0; i<8; i++)
for(int j=0; j<8; j++)
if(mp[i][j] == 'W')
is(0, 'B', i, j);
else if(mp[i][j] == 'B')
is(1, 'W', i, j);
}
bool print(bool isprint, int p)
//p为当前操作者,isprint表示是否输出,便于将此函数【输出可行位置】【查询是否有可行位置】两用
{
bool flag = false;
for(int i=0; i<8; i++)
for(int j=0; j<8; j++)
if(f[p][i][j])
{
if(flag && isprint)
cout<<" ";
flag = true;
if(isprint)
cout<<'('<<i+1<<','<<j+1<<")";
else
return flag;
}
if(isprint)
{
if(flag)
cout<<endl;
else
cout<<"No legal move."<<endl;
}
return flag;
}
void move(int x, int y, int p)
{
char ch;
if(p == 0)
ch = 'W';
else
ch = 'B';
mp[x][y] = ch;
for(int i=0; i<8; i++)//表示八个移动方向
{
/*
因为f[p][x][y]保存了(x, y)位置时可行的移动方向,
这一步实际上是为了找出当时保存的那个可以动方向的k值.
&:按位与.
f[p][x][y] 和 (1 << i) 只有在完全相同时,结果才会为true
*/
if(f[p][x][y] & (1 << i))
{
int tx = x-dx[i];//为什么是减不是加呢?嘿嘿 (因为要往回退,与is()函数的操作刚好相反)
int ty = y-dy[i];
while(tx>=0 && ty>=0 && tx<8 && ty<8 && mp[tx][ty] != ch)
{
mp[tx][ty] = ch;
tx -= dx[i];
ty -= dy[i];
}
}
}
}
int main()
{
int n;
cin >> n; //输入游戏局数
while(n--)
{
memset(mp, 0, sizeof(mp));
for(int i =0 ; i < 8; i++) //输入棋盘
cin >> mp[i]; //每次接收一行
char ch[5]; //ch既用来保存输入的黑/白棋, 也用来保存输入的指令...
cin >> ch;
int p; //输入的黑白棋:ch(W/B) ----> p(0/1)
if(ch[0] == 'W')
p = 0;
else
p = 1;
while(ch[0] != 'Q')
{
//每次都提前先求出黑棋和白棋可行的位置保存下来!
memset(f, 0, sizeof(f));
init();
//输入指令
cin >> ch;
if(ch[0] == 'L')
print(true, p); //打印可行棋子
else if(ch[0] == 'M')
{
if(!print(false, p)) //判断是否存在可行棋子
p ^= 1; //更换玩家 - 使用异或运算符(不同时为1,相同时为0)
move(ch[1]-'0'-1, ch[2]-'0'-1, p);//从指令中获取坐标(ch[1]-'0'-1, ch[2]-'0'-1)
//更换玩家,同时计算此时黑白棋子总数并输出
p ^= 1;
int wnum = 0;
int bnum = 0;
for(int i=0;i<8;i++)
for(int j=0;j<8;j++)
{
if(mp[i][j] == 'W')
++wnum;
if(mp[i][j] == 'B')
++bnum;
}
printf("Black - %2d White - %2d\n", bnum, wnum);
}
}
//输出当前棋盘
for(int i=0; i<8; i++)
cout << mp[i] << endl;
if(n != 0)
cout << endl;
}
return 0;
}