五子棋(Version.2 函数模拟迭代解法)

题面:

张学长和孙学长最近迷上了五子棋,但是看着棋局下棋总感觉差点什么东西——高手的风范,于是两人就开始下盲棋,但是两人的脑阔都不是太灵光,没办法记下所有的落子,所以就先写下落子的坐标后再去棋盘落子,最后才知道是那个获胜,这里将棋盘看成是一个N*N (N=20) 的二维矩阵,刚开始时通过摇骰子的方式决定那个先手(点数大的先手,不存在点数相等的情况),每一次落子都是(i ,j)1<=i,j<=20 表示落子的坐标,问经过M(9<=M<=60)次落子后是未分输赢还是有赢方,由于两人刚开始下盲棋,所以不知道到底有没有人获胜,所以哪怕有一方已经获胜了也还会继续下棋,当两人都感觉到有一方获胜后才会停止下棋(也可能会感觉错^_^)。

输入格式:

第一行输入A,B分别表示张学长的点数和孙学长的点数
第二行输入M。接下来M行每行输入一个落子的坐标。

输出格式:

如果未分输赢只需要输出Draw即可,否则就先输出赢家的编号(张学长编号为A,孙学长编号为B)空格后输出赢家落了多少棋子就赢了。

输入样例:

1 6
12
6,10
5,11
7,11
6,12
5,9
7,13
8,12
8,14
4,8
9,13
4,10
9,15

输出样例:

B 5

样例棋局:

image.png

题解思路:

本质上是模拟题,在代码深度方面的难度是基本等同于语法题的,但是确实相对冗余繁杂了一些,个人认为,在写这道题的代码前要真正的理解题面在讲述什么,很可能你没有AC的原因只是因为理解错了题意......

说几个比较容易忽略的点:

1.可能很多人和我一样拿到这题的第一时间是想到直接当图论,二维数组模拟直接做的,这里就先要注意:五子棋是一个博弈型游戏,双方的落子必须要区分开,要打上不同的标记。

2.对于五子棋的规则要有基本的了解,获胜的条件就是一条线上(纵向、横向、斜向)连通了大于等于5个棋子(注意是存在大于5的情况的)。

3.可以适当分析简化题目中的操作,因为有没有人获胜是由我们设计的代码系统实时判断的,那么一定是由于某一次的落子之后才会首次产生胜利者,在落下这颗关键的棋子之前,不可能提前产生胜利者。

明确上述各点之后,做这题会顺畅很多。由于本质上就是模拟题,做法是非常之多的,首先就不一定是使用图论的思想(笑),之所以提出这种迭代模拟的解法,主要是考虑到不是所有人都能在赛时反应过来用更简化的递归、回溯等算法,或者用得不熟练,而这题是不那么复杂的固定层数问题, 完全可以用比较容易想到的正向思维迭代算法直接模拟出来,于是谨在这里提出个人的思路。

题解代码:

代码看起来确实比较长,但是我这里是为了方便写题解,把所有步骤(包括相似重复的部分)的底层实现都放出来写的。实际做题过程中,完全可以把判断的步骤封装在一个方法函数中,大大节省代码量(初步估计可以节约大概一半,所以最终也就60~70行可以解决)。

OS:所以这题比起考编程能力,个人认为更侧重于对应用型思维和封装能力的考察,思路转得过来和转不过来的两种做题过程会有比较大的差异。

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

using namespace std;
const int N=25;
int G[N][N];            //用二维矩阵形式存储棋盘的状态
int dt[]={1, 2, 3, 4};    //用一维数组存储下标的偏移量,因为是五子棋,只用偏移4次即可

bool Check(int, int, int);

int main(void){
    int pa, pb;        //pa,pb分别代表张、孙的点数
    char op;
      cin>>pa>>pb;

    int m;
      cin>>m;

    for(int i=1;i<=m;i++){
        int x, y;
          cin>>x>>op>>y;
        //输入下标,存在G数组内部,形成棋盘的格局

        if(i%2==1){
            if(pa>pb) G[x][y]=1;        //这里通过奇偶判断+点数大小对比,固定代表张的落子都为“1”,孙的落子都为“2”
            else G[x][y]=2;
        }else{
            if(pa>pb) G[x][y]=2;
            else G[x][y]=1;
        }

        if(Check(x, y, G[x][y])){    //判断是否有人胜出
            if(pa>pb){            //这里的处理与上文类似,判断胜利者的编号及落子数
                if(G[x][y]==1) cout<<"A "<<(i+1)/2<<'\n';
                        //可以推出,对于先手的人来说,他当前总共的落子数是((i+1)/2)
                                //对于后手的人来说,他当前落子数量是i/2
                else cout<<"B "<<i/2<<'\n';
            }else{
                if(G[x][y]==2) cout<<"B "<<(i+1)/2<<'\n';
                else cout<<"A "<<i/2<<'\n';
            }

            return 0;        //当有人提前胜出,程序也提前结束
        }
    }

      cout<<"Draw\n";
    //经过上述判断之后程序没有提前结束,即说明是不分胜负的情况
    return 0;
}


bool Check(int x, int y, int w){
    //判断是否有人胜出,x与y代表棋盘的坐标{x,y},w代表当前下的是谁的棋子
    //因为之前代码的处理,已经明确1是张的落子,2是孙的落子,这里就只需要判断w是1还是2

    int res=1;
    //res保存当前能够连在一起的棋子数量,初始是1(这次落下的1个)

    //下列8个循环,两两作为一组,方便用来判断当前棋子
    //1.纵向有没有连出大于等于5个的棋子
    //2.横向有没有连出大于等于5个的棋子
    //3.自右上向左下的方向有没有连出大于等于5个的棋子
    //4.自左上向右下的方向有没有连出大于等于5个的棋子

    //这里是为了便于读者理解内部底层实现所以写出8个相似的循环,
    //实际上如果理解了,完全可以把这8个循环封装在一个执行函数里面,至少可以节省一半代码量

    for(int i=0;i<4;i++){    //判断纵向
        int nx=x+dt[i];    //相当于是把x坐标进行四次操作,分别是x=x+1, x=x+2, x=x+3, x=x+4,迭代5次,判断有没有连出五个相同棋子
        if(nx<=20&&G[nx][y]==w) res++;    //没有越出棋盘边界,且是相同的棋子,相连棋子的数量res就可以+1

        //在对下标进行移动时,严格注意最大范围要求,在这种做法中,本题棋盘的上界是20,下界是1
        //所以在增加下标时要保证增加之后<=20,减少时保证减少之后>=1

        else break;
        //判断出已经抵达棋盘边界或存在另一种棋子,及时结束循环,进入之后的判断
    }
    for(int i=0;i<4;i++){
        int nx=x-dt[i];    //相当于是把x坐标进行四次操作,分别是x=x-1, x=x-2, x=x-3, x=x-4,迭代5次,判断有没有连出五个相同棋子
        if(nx>=1&&G[nx][y]==w) res++;
        else break;
    }
    if(res>=5) return true;    //判断出已经存在大于等于5个棋子相连了,代表有人已经胜出,返回true

    //以下就是代码、判断与上面完全相同,仅仅作用不同的部分,是完全可以封装起来的
    res=1;    //注意对每一种方向进行判断时,对res进行初始化,因为横向纵向斜向的棋子是不能连在一起讨论的
    for(int i=0;i<4;i++){
        int ny=y+dt[i];
        if(ny<=20&&G[x][ny]==w) res++;
        else break;
    }
    for(int i=0;i<4;i++){
        int ny=y-dt[i];
        if(ny>=1&&G[x][ny]==w) res++;
        else break;
    }
    if(res>=5) return true;

    res=1;
    for(int i=0;i<4;i++){
        int nx=x-dt[i], ny=y+dt[i];        //等同于对x-1~4, y+1~4
        if(nx>=1&&ny<=20&&G[nx][ny]==w) res++;
        else break;
    }
    for(int i=0;i<4;i++){
        int nx=x+dt[i], ny=y-dt[i];
        if(nx<=20&&ny>=1&&G[nx][ny]==w) res++;
        else break;
    }
    if(res>=5) return true;

    res=1;
    for(int i=0;i<4;i++){
        int nx=x-dt[i], ny=y-dt[i];
        if(nx>=1&&ny>=1&&G[nx][ny]==w) res++;
        else break;
    }
    for(int i=0;i<4;i++){
        int nx=x+dt[i], ny=y+dt[i];
        if(nx<=20&&ny<=20&&G[nx][ny]==w) res++;
        else break;
    }
    if(res>=5) return true;

    return false;
    //当最终都没有发现大于等于5个棋子相连的情况,说明没有人提前胜出,返回false
}

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值