题解:P5380 [THUPC 2019] 鸭棋
题解:P5380 [THUPC 2019] 鸭棋
前置芝士:模拟。
推荐题目:
写在前面
这道题是本人第一道独立完成的大模拟,前后一共用了 \(15\) 个小时左右,特写此文以示纪念。
在完成的过程中,我有好几次卡壳的地方:
- 绊脚的位置(马,象,鸭各一次)。
- 判断将军(一次)。
- 坐标的位置(反了一次)。
- 判断游戏结束(忘记一次)。
这些等会儿会重点讲。
以下正文中,如出现 \(cpp\) 框,要么是代码,要么是模拟棋盘。模拟棋盘中,\(wu\) 表示空格,\(ba\) 表示绊脚,其他棋子用拼音中的前两个字母表示,如鸭就是 \(ya\),马就是 \(ma\)。
那么我们废话不多说,开始进入正文。
题意解析
鸭棋是一款与象棋高度相似的博弈棋类游戏,大部分规则与象棋相同,不同的部分如下列举:
- 将象棋中的炮转换为鸭(整个程序里
最烦的棋子),有两个绊脚点,位置分布为:
wu wu wu wu
wu wu ba wu
ya ba wu wu
即对于任意 \(s_x,s_y\in \left\{ 1,-1\right\}\),分别有:
- 如果位置 \(\left(x+s_x\times 2 ,y+s_y \times 1\right),\left(x+s_x\times 1 ,y\right)\) 上均无任意一方的棋子停留,则 \(\left( x+s_x \times 3,y+s_y \times 2\right)\) 为一个可达的位置。
- 如果位置 \(\left(x+s_x \times 1 ,y+ s_y \times 2 \right),\left(x ,y+ s_y \times 1 \right)\) 上均无任意一方的棋子停留,则 \(\left( x+s_x \times 2,y+s_y \times 3\right)\) 为一个可达的位置。
- 象棋中的兵只有四个方向,但鸭棋中的兵有八个方向。
- 鸭棋中没有限制王和士移动的九宫格,因此无需判断王和士是否走出九宫格(
还算良心)。
在鸭棋中,输入的部分为四个坐标,前两个坐标 \((x_1,y_1)\) 表示棋子原来的坐标,后两个坐标 \((x_2,y_2)\) 表示棋子移动后的坐标,我们要根据这些棋子的信息判断游戏是否结束,移动是否合法,是否有将军情况。
输出的部分分为两种:
- 如果该操作为不合法操作,则请输出
Invalid command。 - 如果为合法操作,则依次回答如下的四个问题:
- 被移动的棋子用
[颜色] [类型](注意中间包含空格)来描述(类型要求使用英文名称)。如,红象为red elephant,蓝王为blue captain。 - 被移出游戏的棋子的描述方式与上面类似。特别地,如果无棋子被移出游戏,则该问题的答案为
NA。 - 用
yes、no分别表示形成、不形成将军局面。 - 用
yes、no分别表示游戏结束、游戏不结束。 - 特别的,用
;(分号)将所有问题的答案隔开。
- 被移动的棋子用
如还有不理解的地方,可以多次细读题目,或者去讨论区问问,希望大家都可以理解题目。
思路详解
接下来,我们将一步步将卡壳攻克,带领大家 AC 此题。
棋盘存储
首先,我们要先解决棋盘的存储。作者身边的很多神犇都是用了二维字符数组,方便省力。但作者却运用了别的方法:用序号标注每一个棋子,将每一个棋子的信息都精准无误的存在结构体里,包括坐标,存活状态,名字,虽然烦了些,但也更加清晰明了(吗?)。
特别的,作者一开始将坐标看成了左上角为 \((1,1)\),所以写的代码都是以左上角为 \((1,1)\) 的。
结构体:
struct duck {
int dx,dy,xh;
//dx 和 dy 分别是 x 坐标和 y 坐标。
string zl,ys;
//zl 是棋子的种类,ys 是颜色。
bool sm=true;
//sm 是是否存活。
}a[110];
结构体初始化函数
void fuzhi() {
for(int i=1;i<=n;i++) {
//n 是棋子的总个数(32)。
if(i<=n/2) {
a[i].ys="blue";
}
else {
a[i].ys="red";
}
if(i==1||i==9||i==24||i==32) {
//初始化车。
if(i==1) {
a[i].dx=1;
a[i].dy=1;
}
else if(i==9) {
a[i].dx=1;
a[i].dy=9;
}
else if(i==24) {
a[i].dx=10;
a[i].dy=1;
}
else {
a[i].dx=10;
a[i].dy=9;
}
a[i].zl="car";
}
if(i==2||i==8||i==25||i==31) {
//初始化马。
if(i==2) {
a[i].dx=1;
a[i].dy=2;
}
else if(i==8) {
a[i].dx=1;
a[i].dy=8;
}
else if(i==25) {
a[i].dx=10;
a[i].dy=2;
}
else {
a[i].dx=10;
a[i].dy=8;
}
a[i].zl="horse";
}
if(i==3||i==7||i==26||i==30) {
//初始化象。
if(i==3) {
a[i].dx=1;
a[i].dy=3;
}
else if(i==7) {
a[i].dx=1;
a[i].dy=7;
}
else if(i==26) {
a[i].dx=10;
a[i].dy=3;
}
else {
a[i].dx=10;
a[i].dy=7;
}
a[i].zl="elephant";
}
if(i==4||i==6||i==27||i==29) {
//初始化士。
if(i==4) {
a[i].dx=1;
a[i].dy=4;
}
else if(i==6) {
a[i].dx=1;
a[i].dy=6;
}
else if(i==27) {
a[i].dx=10;
a[i].dy=4;
}
else {
a[i].dx=10;
a[i].dy=6;
}
a[i].zl="guard";
}
if(i==5||i==28) {
//初始化王。
if(i==5) {
a[i].dx=1;
a[i].dy=5;
}
else {
a[i].dx=10;
a[i].dy=5;
}
a[i].zl="captain";
}
if(i>=12&&i<=21) {
//初始化兵。
if(i<=16) {
a[i].dx=4;
}
else {
a[i].dx=7;
}
if(i==12||i==17) {
a[i].dy=1;
}
else if(i==13||i==18) {
a[i].dy=3;
}
else if(i==14||i==19) {
a[i].dy=5;
}
else if(i==15||i==20) {
a[i].dy=7;
}
else {
a[i].dy=9;
}
a[i].zl="soldier";
}
if(i==10||i==11||i==22||i==23) {
//初始化鸭。
if(i==10) {
a[i].dx=3;
a[i].dy=1;
}
else if(i==11) {
a[i].dx=3;
a[i].dy=9;
}
else if(i==22) {
a[i].dx=8;
a[i].dy=1;
}
else {
a[i].dx=8;
a[i].dy=9;
}
a[i].zl="duck";
}
//初始化序号。
a[i].xh=i;
}
}
主函数与辅助函数
初始完结构体,再来看主函数,作者将轮次变换放在了主函数:
signed main() {
cin>>q;
n=32;
fuzhi();
while(q--) {
solve();
//solve() 函数是最重要的判断部分,后期重点详解。
if(f) {
//lun 指当前轮次是那个颜色的棋子移动,方便后续判断。
if(lun=="red") {
lun="blue";
}
else {
lun="red";
}
}
}
}
在正式进入操作前,我们还需要以下变量的初始化和一些判断函数。
- 判断游戏是否结束的函数,每一次操作前都要优先判断:
string jieshu() {
//序号 5 是蓝色王的编号,序号 28 是红色王的编号。
if(!a[5].sm) {
//蓝王死了,红方赢。
return "redwin!";
}
else if(!a[28].sm) {
//红王死了,蓝方赢。
return "bluewin!";
}
else {
//都没死,谁也没赢。
return "nowin!";
}
}
- 更新坐标的函数,将合法操作的棋子由旧坐标钻尾新坐标:
void move(int i,int qx,int qy) {
//qx 和 qy 是新的 x 坐标和 y 坐标。
a[i].dx=qx;
a[i].dy=qy;
}
- 判断每次输入的四个坐标是否合法的两个函数:
判断前两个坐标的函数:
bool check(int x,int y) {
for(int i=1;i<=n;i++) {
//有任意一颗与当前轮次同颜色棋子,在将被移动的位置就返回 true。
if(a[i].sm&&a[i].dx==x&&a[i].dy==y&&a[i].ys==lun) {
return true;
}
}
//没有任意一颗与当前轮次同颜色棋子,在将被移动的位置就返回 false。
return false;
}
判断后两个坐标的函数:
bool check1(int x,int y) {
for(int i=1;i<=n;i++) {
//有任意一颗与当前轮次同颜色的棋子,在将被移动到的位置就返回 false。
if(a[i].sm&&a[i].dx==x&&a[i].dy==y&&a[i].ys==lun) {
return false;
}
}
//没有任意一颗与当前轮次同颜色的棋子,在将被移动到的位置就返回 true。
return true;
}
- 由于作者开始时将坐标看错了,所以还需要一个转换坐标的函数:
int zhhu(int x) {
//左上角和右上角坐标的转换为 10-x。
return 10-x;
}
- 主要操作中需要遇到的变量:
int x,y,x1,y1,p;
bool f=false,yi=false;
//p 表示本次移动的棋子序号,f 表示操作是否合法,yi 表示是否有棋子死亡。
主要操作
从现在开始,我们正式进入主要操作,这是这道题中,难度最大,码量最大,细节最多的部分。
转换坐标与初步判断合法
在转换坐标中,\(x\) 可以通过之前写过的转换函数进行转换,\(y\) 坐标只需 \(+1\) 即可(\(0\) 与 \(1\) 的差为 \(1\)),代码如下:
cin>>x>>y>>x1>>y1;
x=zhhu(x);
x1=zhhu(x1);
y++;
y1++;
初步判断需要用到题目中的不合法说明,具体有以下几点:
- 游戏已经结束了。
- \((x,y)\) 与 \((x_1,y_1)\) 相同。
- \((x,y)\) 和 \((x_1,y_1)\) 超出了棋盘的范围。
- 违反了两个判断函数中的条件。
代码实现很简单,具体如下:
if(jieshu()=="bluewin!"||jieshu()=="redwin!") {
cout<<"Invalid command\n";
return;
}
if(x==x1&&y==y1) {
cout<<"Invalid command\n";
return;
}
if(x>jx||y>jy||x1>jx||y1>jy||x<1||y<1||x1<1||y1<1) {
//jx 和 jy 为棋盘的极限范围,jx 为 10,jy 为 9。
cout<<"Invalid command\n";
return;
}
//以下是刚刚提到的两个判断函数。
if(!check(x,y)) {
cout<<"Invalid command\n";
return;
}
if(!check1(x1,y1)) {
cout<<"Invalid command\n";
return;
}
具体判断每一颗棋子
可以从 \(1\)~\(n\) 的序号中找到棋子的对应序号,再根据每一个棋子的特点进行判断。
王(captain)
王的移动为上下左右四格,要么 \(x=x_1\) 且 \(|y_1-y|=1\);要么 \(|x_1-x|=1\) 且 \(y=y_1\)。代码简单易懂。
if(a[i].zl=="captain") {
if((abs(x1-x)==1&&!abs(y1-y))||(!abs(x1-x)&&abs(y1-y)==1)) {
//判断条件。
f=true;
//此次操作为合法。
move(i,x1,y1);
//进行移动。
p=i;
//记录序号。
}
else {
cout<<"Invalid command\n";
return;
}
}
之后的代码中 f,move(i,qx,qy) 和 p 的含义不再赘述,上文皆有提及。
士(guard)
士的移动为斜向的移动,判断条件相较于王更为简单,只要 \(|y_1-y|=1\) 且 \(|x_1-x|=1\),那么移动就是合法的。代码如下;
if(a[i].zl=="guard") {
if(abs(x1-x)==1&&abs(y1-y)==1) {
f=true;
move(i,x1,y1);
p=i;
}
else {
cout<<"Invalid command\n";
return;
}
}
象(elephant)
从这里开始,问题才真正变得棘手起来,绊脚点的加入让判断变得复杂多变。但我们可以通过四个大类来逐一攻克:
- \(x_1-x=2\) 且 \(y_1-y=2\),只要 \((x+1,y+1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=2\) 且 \(y_1-y=-2\),只要 \((x+1,y-1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=-2\) 且 \(y_1-y=2\),只要 \((x-1,y+1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=-2\) 且 \(y_1-y=-2\),只要 \((x-1,y-1)\) 处没有棋子,此次操作就是合法的。
每一次判断的时间复杂度都是 \(O(n)\),虽然比前两个的 \(O(1)\) 慢了一些,但对于这个数据范围来说还是极为充足的,代码如下:
if(a[i].zl=="elephant") {
if(abs(x1-x)==2&&abs(y1-y)==2) {
//大的判断条件。
if(x1-x==2&&y1-y==2) {
//判断可能的条件 1。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x+1&&a[j].dy==y+1) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==-2&&y1-y==2) {
//判断可能的条件 2。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x-1&&a[j].dy==y+1) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==2&&y1-y==-2) {
//判断可能的条件 3。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x+1&&a[j].dy==y-1) {
cout<<"Invalid command\n";
return;
}
}
else {
//判断可能的条件 4。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x-1&&a[j].dy==y-1) {
cout<<"Invalid command\n";
return;
}
}
}
f=true;
move(i,x1,y1);
p=i;
}
else {
cout<<"Invalid command\n";
return;
}
}
马(horse)
马的复杂程度是仅次于鸭的。它的合法情况一共有 \(8\) 种:
- \(x_1-x=1\) 且 \(y_1-y=2\),只要 \((x,y+1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=1\) 且 \(y_1-y=-2\),只要 \((x,y-1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=-1\) 且 \(y_1-y=2\),只要 \((x,y+1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=-1\) 且 \(y_1-y=-2\),只要 \((x,y-1)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=2\) 且 \(y_1-y=1\),只要 \((x+1,y)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=2\) 且 \(y_1-y=-1\),只要 \((x+1,y)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=-2\) 且 \(y_1-y=1\),只要 \((x-1,y)\) 处没有棋子,此次操作就是合法的。
- \(x_1-x=-2\) 且 \(y_1-y=-1\),只要 \((x-1,y)\) 处没有棋子,此次操作就是合法的。
代码实现细节较多,大家可以写一步检查一步,不要最后写完了才查出错(别问我是怎么知道的)。
if(a[i].zl=="horse") {
if((abs(x1-x)==1&&abs(y1-y)==2)||(abs(x1-x)==2&&abs(y1-y)==1)) {
//大的判断条件。
if(x1-x==1&&y1-y==2) {
//判断可能的条件 1。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x&&a[j].dy==y+1) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==1&&y1-y==-2) {
//判断可能的条件 2。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x&&a[j].dy==y-1) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==-1&&y1-y==2) {
//判断可能的条件 3。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x&&a[j].dy==y+1) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==-1&&y1-y==-2) {
//判断可能的条件 4。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x&&a[j].dy==y-1) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==2&&y1-y==1) {
//判断可能的条件 5。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x+1&&a[j].dy==y) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==2&&y1-y==-1) {
//判断可能的条件 6。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x+1&&a[j].dy==y) {
cout<<"Invalid command\n";
return;
}
}
}
else if(x1-x==-2&&y1-y==1) {
//判断可能的条件 7。
for(int j=1;j<=n;j++) {
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x-1&&a[j].dy==y) {
cout<<"Invalid command\n";
return;
}
}
}
else {
for(int j=1;j<=n;j++) {
//判断可能的条件 8。
if(i==j) {
continue;
}
if(a[j].sm&&a[j].dx==x-1&&a[j].dy==y) {
cout<<"Invalid command\n";
return;
}
}
}
f=true;
move(i,x1,y1);
p=i;
}
else {
cout<<"Invalid command\n";
return;
}
}

浙公网安备 33010602011771号