P3930 一道大水题 Knight 题解

· · 题解

加上多组数据后一遍就过了并最优解第五很高兴,写一篇题解。

本题解优点在于较详细的搜索步骤解释。

每组数据的核心思路:

  1. 对数据进行初始化,输入棋盘,记下起点(O)和终点(X)的位置。
  2. 采用广搜。建立优先队列,将起点入队。
  3. 取出队首节点(出队)。
  4. 判断如果是终点,则输出当前的步数并直接结束该组数据。
  5. 判断如果有未吃掉的敌方棋子能吃掉该格的马,则回到第三步。
  6. 判断如果该格有敌方棋子,则标记该棋子为被吃掉状态。
  7. 遍历马的方向数组,把该节点能通向的未走过节点全部入队。(很板子的广搜思想)
  8. 回到第三步。

其中最难写的部分其实是“判断有没有未吃掉的敌方棋子能吃掉该格的马”。该步骤的方法是遍历敌方的所有棋子,看白马是否在它们的攻击范围内。如果白马在其中任何一枚棋子的攻击范围内,它就会失败。

接下来说说每一种棋子分别应该怎么判断。

城堡(车)

如果白马与车在同一行,则沿列从车开始先后向两侧遍历。如果任意方向碰到了白马,则返回白马被吃掉。如果遇到了其他敌方棋子,则视为攻击被打断,跳出循环。

如果白马与车在同一列,则沿行遍历,其余同上。

骑士(马)

借用广搜使用的马的方向数组。如果黑马往任何一个方向跳时碰到了白马,则返回白马被吃掉。

主教(象)

依次遍历四个方向:行列坐标同时减;行坐标减且列坐标加;行坐标加且列坐标减;行列坐标同时加。如果一个方向上碰到了白马,则返回白马被吃掉。如果碰到了其他黑子,则跳出该方向。

皇后(后)

先按照车的方式判断,再紧接着按照象的方式判断。是的,没什么好改的。

国王(王)

为它单独开一个 8 位的方向数组,然后遍历每一个方向,如果一个方向上碰到了白马,则返回白马被吃掉。

士兵(兵)

直接判断它下一行左一列、下一行右一列两个方向上是否碰到了白马即可。

以上本题的难点就结束了。其实写起来代码只是看着长,并没有很难。

代码中也有必要的注释。

#include<bits/stdc++.h>
using namespace std;
int n,top,sx,sy,ex,ey;
struct node{
    bool flag[65];//每一个敌方棋子是否被吃掉了
    int x,y,k;//坐标,步数
    bool operator <(const node &b) const{
        return k>b.k;
    }
}a;
struct chess{
    char c;
    int x,y;
}f[65];
map<pair<int,int>,int> ma;
char c[65][65];
bool fl[65][65];
priority_queue<node> q;
int dx[8]={1,1,-1,-1,2,2,-2,-2},dy[8]={2,-2,2,-2,1,-1,1,-1};
bool in(int x,int y){
    if(x<1||x>n) return 0;
    if(y<1||y>n) return 0;
    return 1;
}
bool check(node t){
    int sx=t.x,sy=t.y;
    for(int i=1;i<=top;i++){//遍历所有棋子
        if(t.flag[i]) continue;
        if(f[i].c=='C'){//车
            int x=f[i].x,y=f[i].y;
            if(x==sx){
                for(int j=y-1;j>=1;j--){//在左边
                    if(j==sy) return 1;
                    if(ma.count({x,j})&&!t.flag[ma[{x,j}]]) break;//遇到别的棋子就打断攻击范围
                }
                for(int j=y+1;j<=n;j++){//在右边
                    if(j==sy) return 1;
                    if(ma.count({x,j})&&!t.flag[ma[{x,j}]]) break;
                }
            }
            if(y==sy){
                for(int j=x-1;j>=1;j--){//在上边
                    if(j==sx) return 1;
                    if(ma.count({j,y})&&!t.flag[ma[{j,y}]]) break;
                }
                for(int j=x+1;j<=n;j++){//在下边
                    if(j==sx) return 1;
                    if(ma.count({j,y})&&!t.flag[ma[{j,y}]]) break;
                }
            }
        }
        else if(f[i].c=='K'){//马
            int x=f[i].x,y=f[i].y;
            for(int j=0;j<8;j++){//8个方向,找到了就能杀
                int xx=x+dx[j],yy=y+dy[j];
                if(in(xx,yy)&&sx==xx&&sy==yy) return 1;
            }
        }
        else if(f[i].c=='B'){//象
            int x=f[i].x,y=f[i].y;
            for(int k=1;x-k>=1&&y-k>=1;k++){//左上方
                if(x-k==sx&&y-k==sy) return 1;
                if(ma.count({x-k,y-k})&&!t.flag[ma[{x-k,y-k}]]) break;
            }
            for(int k=1;x+k<=n&&y-k>=1;k++){//左下方
                if(x+k==sx&&y-k==sy) return 1;
                if(ma.count({x+k,y-k})&&!t.flag[ma[{x+k,y-k}]]) break;
            }
            for(int k=1;x-k>=1&&y+k<=n;k++){//右上方
                if(x-k==sx&&y+k==sy) return 1;
                if(ma.count({x-k,y+k})&&!t.flag[ma[{x-k,y+k}]]) break;
            }
            for(int k=1;x+k<=n&&y+k<=n;k++){//右下方
                if(x+k==sx&&y+k==sy) return 1;
                if(ma.count({x+k,y+k})&&!t.flag[ma[{x+k,y+k}]]) break;
            }
        }
        else if(f[i].c=='Q'){//后
            int x=f[i].x,y=f[i].y;
            //车的部分
            if(x==sx){
                for(int j=y-1;j>=1;j--){//在左边
                    if(j==sy) return 1;
                    if(ma.count({x,j})&&!t.flag[ma[{x,j}]]) break;//遇到别的棋子就打断攻击范围
                }
                for(int j=y+1;j<=n;j++){//在右边
                    if(j==sy) return 1;
                    if(ma.count({x,j})&&!t.flag[ma[{x,j}]]) break;
                }
            }
            if(y==sy){
                for(int j=x-1;j>=1;j--){//在上边
                    if(j==sx) return 1;
                    if(ma.count({j,y})&&!t.flag[ma[{j,y}]]) break;
                }
                for(int j=x+1;j<=n;j++){//在下边
                    if(j==sx) return 1;
                    if(ma.count({x,j})&&!t.flag[ma[{j,y}]]) break;
                }
            }
            //象的部分
            for(int k=1;x-k>=1&&y-k>=1;k++){//左上方
                if(x-k==sx&&y-k==sy) return 1;
                if(ma.count({x-k,y-k})&&!t.flag[ma[{x-k,y-k}]]) break;
            }
            for(int k=1;x+k<=n&&y-k>=1;k++){//左下方
                if(x+k==sx&&y-k==sy) return 1;
                if(ma.count({x+k,y-k})&&!t.flag[ma[{x+k,y-k}]]) break;
            }
            for(int k=1;x-k>=1&&y+k<=n;k++){//右上方
                if(x-k==sx&&y+k==sy) return 1;
                if(ma.count({x-k,y+k})&&!t.flag[ma[{x-k,y+k}]]) break;
            }
            for(int k=1;x+k<=n&&y+k<=n;k++){//右下方
                if(x+k==sx&&y+k==sy) return 1;
                if(ma.count({x+k,y+k})&&!t.flag[ma[{x+k,y+k}]]) break;
            }
        }
        else if(f[i].c=='X'){//王
            int x=f[i].x,y=f[i].y;
            int ddx[8]={-1,-1,-1,0,1,1,1,0},ddy[8]={-1,0,1,1,1,0,-1,-1};
            for(int j=0;j<8;j++){//身边八个格子,找到了就能杀
                if(in(x+ddx[j],y+ddy[j])&&x+ddx[j]==sx&&y+ddy[j]==sy) return 1;
            }
        }
        else if(f[i].c=='P'){//兵
            int x=f[i].x,y=f[i].y;
            if(in(x+1,y-1)&&x+1==sx&&y-1==sy) return 1;
            if(in(x+1,y+1)&&x+1==sx&&y+1==sy) return 1;//左右下方两个格子
        }
    }
    return 0;
}
int main(){
    while(cin>>n){
        while(!q.empty()) q.pop();
        ma.clear(),top=0;//初始化
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){
            cin>>c[i][j];
            if(c[i][j]=='X') ex=i,ey=j;//存终点
            if(c[i][j]=='O') a.x=i,a.y=j;//存起点
            else if(c[i][j]!='.') f[++top]={c[i][j],i,j},ma[{i,j}]=top;//存入敌方棋子
            fl[i][j]=0;//初始化
        }
        a.k=0;
        for(int i=1;i<=55;i++) a.flag[i]=0;//初始化
        q.push(a);//起点带着它的一大坨信息入队
        bool flag=0;
        while(!q.empty()){
            node t=q.top();q.pop();
            if(t.x==ex&&t.y==ey){//到终点了
                cout<<t.k<<"\n";
                flag=1;
                break;
            }
            if(check(t)) continue;//被棋子吃掉
            t.k++;
            if(ma.count({t.x,t.y})) t.flag[ma[{t.x,t.y}]]=1;//吃掉棋子
            for(int i=0;i<8;i++){//下一步走法
                t.x+=dx[i],t.y+=dy[i];
                if(in(t.x,t.y)&&!fl[t.x][t.y]){//能走且没走过
                    fl[t.x][t.y]=1,q.push(t);
                }
                t.x-=dx[i],t.y-=dy[i];
            }
        }
        if(!flag) cout<<-1<<"\n";//不可能
    }
    return 0;
}