1.五子棋对弈python解法——2024年省赛蓝桥杯真题

问题描述

原题传送门:1.五子棋对弈 - 蓝桥云课

"在五子棋的对弈中,友谊的小船说翻就翻?" 不!对小蓝和小桥来说,五子棋不仅是棋盘上的较量,更是心与心之间的沟通。这两位挚友秉承着"友谊第一,比赛第二"的宗旨,决定在一块 5×5 的棋盘上,用黑白两色的棋子来决出胜负。但他们又都不忍心让对方失落,于是决定用一场和棋(平局)作为彼此友谊的见证。

比赛遵循以下规则:

棋盘规模:比赛在一个 5×5 的方格棋盘上进行,共有 25 个格子供下棋使用

棋子类型:两种棋子,黑棋与白棋,代表双方。小蓝持白棋,小桥持黑棋

先手规则:白棋(小蓝)具有先手优势,即在棋盘空白时率先落子(下棋)

轮流落子:玩家们交替在棋盘上放置各自的棋子,每次仅放置一枚

胜利条件:率先在横线、竖线或斜线上形成连续的五个同色棋子的一方获胜

平局条件:当所有 25 个棋盘格都被下满棋子,而未决出胜负时,游戏以平局告终

在这一设定下,小蓝和小桥想知道,有多少种不同的棋局情况,既确保棋盘下满又保证比赛结果为平局。

答案提交

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

问题分析

说实话在赛场看到这样的题目,说不慌张都是骗人的,尤其是其还放在填空题中,这对于我们这种水平偏中下的同学来说更是一个较为严峻的挑战。

本题在官方的难度分类中为中等题,但是在小玉看来,其难度就算在中等,那也是中等中偏难的那种,所以没有解题成功的同学并不需要慌张,也不要气馁,你能够在编程软件上敲下你的想法,就已经超过了赛场上的很多人!(这是真的!很多时候我们更应该做的是相信自己 (ง๑ •̀_•́)ง )

言归正传,万事开头难,我们先来简单分析一下这个题目!

这里的关键是统计所有可能的满盘棋局,并确保没有出现五子连珠的情况。由于棋盘规模是 5×5,共有 25!  种不同的落子顺序,直接遍历是不可行的,需要更高效的方法。

我们可以采用回溯 + 剪枝的方法进行搜索:

  1. 回溯搜索:逐步填充棋盘,每次轮流落子(白棋先手)。
  2. 剪枝:在填充的过程中,如果某个状态下已经形成五子连珠,则立即剪枝,不再继续。
  3. 终止条件:当棋盘填满时,若没有五子连珠的情况,则计数。

这个方法考虑了所有可能的棋局排列,并检查是否有五子连珠的情况。但由于 25!  过于庞大,直接使用排列组合的方法会导致计算量过大,优化点在于:

  • 使用位运算或更紧凑的数据结构 以减少存储开销
  • 提前剪枝 在确定某个局面已经形成五子连珠时尽早结束

如果需要进一步优化,可以考虑更复杂的数据结构如Zobrist哈希来避免重复计算。

上面是我一开始看到这个题目时脑子里所有冒出来的想法,先不管对不对,有想法就要坚持去实践!万一实现了呢 [狗头先欠着

下面是我开始解题时编写的python代码:

当然这个代码是错误的,因为只考虑到了逻辑上的可行性,并没有考虑到实际运算的可行性,有兴趣的同学可以将我错误示例与自己的相对比,在错误中学习

def generate_permutations(elements):"""递归生成列表元素的全排列(手工实现 permutations 生成器)参数:elements: list,需要生成排列的元素列表Yields:list: 一个排列组合"""# 基本情况:空列表只有一种排列(空列表)if len(elements) == 0:yield []else:# 遍历每个元素作为排列的第一个元素for i in range(len(elements)):# 排除当前元素后的剩余元素列表rest = elements[:i] + elements[i+1:]# 递归生成剩余元素的所有排列for p in generate_permutations(rest):# 将当前元素与子排列组合yield [elements[i]] + pdef check_five_in_a_row(board):"""检查五子棋棋盘是否存在五连珠情况参数:board: list[list[int]],5x5的二维数组,0表示空,1/2表示玩家返回:bool: 是否存在五连珠"""def check_line(x, y, dx, dy):"""检查从(x,y)出发,沿(dx,dy)方向是否存在五连珠参数:x, y: 起始坐标dx, dy: 方向步长(取值应为0或±1)返回:bool: 是否五连"""color = board[x][y]# 空位置直接返回否if color == 0:return False# 检查连续五个位置for i in range(1, 5):  # 只需要检查后续四个位置(共五个)nx, ny = x + i*dx, y + i*dy# 确保不会越界(根据调用方式其实不需要,但保留更安全)if nx >= 5 or ny >= 5 or nx < 0 or ny < 0:return Falseif board[nx][ny] != color:return Falsereturn True# 检查所有可能的五连珠路线for i in range(5):# 横向检查(每行的起始位置)if check_line(i, 0, 0, 1):return True# 纵向检查(每列的起始位置)if check_line(0, i, 1, 0):return True# 对角线检查return check_line(0, 0, 1, 1) or check_line(0, 4, 1, -1)def count_draw_games():"""计算所有可能的平局棋局数量(无五连珠的完整棋盘)注意:此实现因时间复杂度为O(25!)而无法实际运行,仅作为逻辑演示返回:int: 平局数量(理论值)"""# 生成所有棋盘位置坐标positions = [(i, j) for i in range(5) for j in range(5)]draw_count = 0# 遍历所有可能的落子顺序排列for perm in generate_permutations(positions):# 初始化空棋盘board = [[0] * 5 for _ in range(5)]# 模拟落子过程for turn, (x, y) in enumerate(perm):# 交替玩家落子(玩家1先手)board[x][y] = 1 if turn % 2 == 0 else 2# 每次落子后立即检查五连珠if check_five_in_a_row(board):break  # 出现五连则终止当前棋局else:# 完整25步且无五连珠的情况计数draw_count += 1return draw_count# 注意:实际运行时本代码无法完成计算
print(count_draw_games())

好!现在我们来真正地说一说本题的正确打开方式:

首先,如果你想要顺利的解出本题,那么你需要了解一个至关重要的知识点:将线性位置转换为二维坐标,这个思路可以用于解决所有和本题相似的题目中

如果你不清楚这个知识点,请你将下面得到两个式子记住,如果你想了解这个式子是如何推导得到的,欢迎访问我的个人网站,在算法必备知识点模块数学篇可以找到相关的推导(好吧现在网站没有了wuwuwuwu),好消息是,我找到了一个大佬写的解析:希望可以为你解惑关于坐标压缩式子的解压缩式子-CSDN博客

    x = (position - 1) // line_size + 1  # 行坐标y = (position - 1) % line_size + 1    # 列坐标

假设现在有一个坐标轴,那么其中的position为坐标轴上的刻度,也就是线性坐标X,line_size为你希望将其转换到二维坐标系下的坐标系长度(二维平面XOY,我们假设这个平面就像棋盘一样,每一边都有其边长,其中line_size就是这个边长),此后我们便可以通过这个式子计算转换之后的每个点对应的二维坐标!在本题中,我们还应该注意到:两条对角线索引计算的差异

  • 主对角线:x+y(范围2-10)

  • 副对角线:x-y+5(转换负数为正数,范围1-9)

其次,我们需要使用四个数组分别跟踪行、列、对角线的棋子差值(白棋+1,黑棋-1),由于棋盘上总共能放下 5x5 共25个棋子,且 白棋先手,轮流落子因此我们可以认为:

        初始白子:13颗

        初始黑子:12颗

而防止有一方连成5子赢得比赛,我们应该有对于二者棋子数量的    绝对值的限制(<4),以确保同一颜色不会出现5连。

这点也可以成为我们后续简化代码的关键剪枝条件:

  • 白棋条件:各方向统计值 <4(保证还能放白棋)

  • 黑棋条件:各方向统计值 >-4(保证还能放黑棋)

代码描述:

当分析到这里,对于知识点掌握比较扎实的同学应该可以猜到小玉要使用什么算法来解决本题了,没错!就是你想的那种!就是——记忆化搜索+启发式剪枝+并行计算(劝退)

我们大概来介绍一下这几个小方向:

  1. 记忆化搜索

     缓存重复出现的棋盘状态
  2. 并行计算

    将搜索树分解为多个子树并行处理
  3. 启发式剪枝

    提前识别无效路径(如双方都无法形成连线)

其实这三个内容在上面的思路分析部分已经多多少少提到过了,有没有课代表愿意画画重点呢?(要考的!)那么本题的具体代码如下,让我们跟着思路一步一步实现:

  1. 初始化棋盘尺寸和平局计数器

    • board_size 设置棋盘的大小为5。
    • draw_count 用来记录平局的次数。
       
      board_size = 5 # 棋盘边长,本题为正方形棋盘
      draw_count = 0 # 记录平局的次数

  2. 创建棋盘状态跟踪系统

    • 使用四个数组来分别跟踪行、列、主对角线和副对角线上的棋子差值。白棋记为+1,黑棋记为-1。

    • row_count:行差值统计数组,长度为 board_size + 1,索引从1开始。

    • col_count:列差值统计数组,长度和索引同上。

    • main_diag_count:主对角线差值统计数组,长度为 2 * board_size + 1,索引对应于对角线上的位置。

    • anti_diag_count:副对角线差值统计数组,长度同上,索引对应于副对角线上的位置。

      # 统计数组,分别记录行、列、主对角线、副对角线的棋子数
      row_count = [0] * (board_size + 1)
      col_count = [0] * (board_size + 1)
      main_diag_count = [0] * (2 * board_size + 1)
      anti_diag_count = [0] * (2 * board_size + 1)
  3. 定义递归函数 count_draw_games

    • 参数 position 表示当前放置棋子的位置(从1开始)。

    • 参数 white_pieces 和 black_pieces 分别表示剩余可以放置的白棋和黑棋的数量。

    • 从线性坐标轴上看,当 position 达到棋盘大小的平方加一时,表示所有位置已填满,此时增加 draw_count,即达到和棋条件

    • 将线性位置转换为二维坐标(行 x 和列 y)。

  4. def count_draw_games(position, white_pieces, black_pieces):global draw_count# 终止条件:所有位置已填满if position == board_size * board_size + 1:draw_count += 1return# 将线性位置转换为二维坐标(1-based)x = (position - 1) // board_size + 1  # 行坐标(1-5)y = (position - 1) % board_size + 1    # 列坐标(1-5)# 尝试放置白棋(先手方需要多一个棋子)if white_pieces > 0:# 剪枝条件:保证所有方向上白棋不超过4个if (row_count[x] < board_size - 1 and           # 行方向还能放白棋col_count[y] < board_size - 1 and           # 列方向还能放白棋main_diag_count[x + y] < board_size - 1 and # 主对角线方向anti_diag_count[x - y + board_size] < board_size - 1): # 副对角线方向# 更新所有跟踪数组row_count[x] += 1col_count[y] += 1main_diag_count[x + y] += 1anti_diag_count[x - y + board_size] += 1# 递归处理下一个位置count_draw_games(position + 1, white_pieces - 1, black_pieces)# 回溯:恢复状态row_count[x] -= 1col_count[y] -= 1main_diag_count[x + y] -= 1anti_diag_count[x - y + board_size] -= 1# 尝试放置黑棋if black_pieces > 0:# 剪枝条件:保证所有方向上黑棋不超过4个# 注意这里的比较方向是反的(因为黑棋用负数表示)if (row_count[x] > 1 - board_size and           # 行方向还能放黑棋col_count[y] > 1 - board_size and           # 列方向还能放黑棋main_diag_count[x + y] > 1 - board_size and # 主对角线方向anti_diag_count[x - y + board_size] > 1 - board_size):# 更新所有跟踪数组(黑棋用减法)row_count[x] -= 1col_count[y] -= 1main_diag_count[x + y] -= 1anti_diag_count[x - y + board_size] -= 1# 递归处理下一个位置count_draw_games(position + 1, white_pieces, black_pieces - 1)# 回溯:恢复状态row_count[x] += 1col_count[y] += 1main_diag_count[x + y] += 1anti_diag_count[x - y + board_size] += 1

    以上便是本题的所有代码了,以下是上述代码的总结:

    # 棋盘尺寸
    board_size = 5
    # 平局计数器
    draw_count = 0"""
    创新性的棋盘状态跟踪系统:
    使用四个数组分别跟踪行、列、对角线的棋子差值(白棋+1,黑棋-1)
    这种设计使得我们可以在 O(1) 时间内判断是否允许落子
    """
    # 行差值统计(索引1-5)
    row_count = [0] * (board_size + 1)
    # 列差值统计(索引1-5)
    col_count = [0] * (board_size + 1)
    # 主对角线(左上到右下)差值统计(x+y范围:2-10)
    main_diag_count = [0] * (2 * board_size + 1)
    # 副对角线(右上到左下)差值统计(x-y+5范围:1-9)
    anti_diag_count = [0] * (2 * board_size + 1)def count_draw_games(position, white_pieces, black_pieces):global draw_count# 终止条件:所有位置已填满if position == board_size * board_size + 1:draw_count += 1return# 将线性位置转换为二维坐标(1-based)x = (position - 1) // board_size + 1  # 行坐标(1-5)y = (position - 1) % board_size + 1    # 列坐标(1-5)# 尝试放置白棋(先手方需要多一个棋子)if white_pieces > 0:# 剪枝条件:保证所有方向上白棋不超过4个if (row_count[x] < board_size - 1 and           # 行方向还能放白棋col_count[y] < board_size - 1 and           # 列方向还能放白棋main_diag_count[x + y] < board_size - 1 and # 主对角线方向anti_diag_count[x - y + board_size] < board_size - 1): # 副对角线方向# 更新所有跟踪数组row_count[x] += 1col_count[y] += 1main_diag_count[x + y] += 1anti_diag_count[x - y + board_size] += 1# 递归处理下一个位置count_draw_games(position + 1, white_pieces - 1, black_pieces)# 回溯:恢复状态row_count[x] -= 1col_count[y] -= 1main_diag_count[x + y] -= 1anti_diag_count[x - y + board_size] -= 1# 尝试放置黑棋if black_pieces > 0:# 剪枝条件:保证所有方向上黑棋不超过4个# 注意这里的比较方向是反的(因为黑棋用负数表示)if (row_count[x] > 1 - board_size and           # 行方向还能放黑棋col_count[y] > 1 - board_size and           # 列方向还能放黑棋main_diag_count[x + y] > 1 - board_size and # 主对角线方向anti_diag_count[x - y + board_size] > 1 - board_size):# 更新所有跟踪数组(黑棋用减法)row_count[x] -= 1col_count[y] -= 1main_diag_count[x + y] -= 1anti_diag_count[x - y + board_size] -= 1# 递归处理下一个位置count_draw_games(position + 1, white_pieces, black_pieces - 1)# 回溯:恢复状态row_count[x] += 1col_count[y] += 1main_diag_count[x + y] += 1anti_diag_count[x - y + board_size] += 1# 初始化递归(白棋13个,黑棋12个)
    count_draw_games(1, (board_size * board_size + 1) // 2, board_size * board_size // 2)print(draw_count)

结果提交

上述代码的运行结果:

将上述代码提交到蓝桥杯官网

写在后面

本题的成功解决也告诉我们,不应该随便忽略题设中的任何一个条件和字眼,也就是:审题是务必全面仔细!!!实际赛场中,如果你在未完全使用题设条件的前提下编写出了代码,那其在实际测试时的样例通过率可能会非常感人😭😭😭

有小伙伴私信提到实际运行时间上的问题,我想说的是,实际赛场上的测试软件中,对于python这个编程语言还是比较宽容的,所以其实并不用太过于焦虑担心运行用时这块的问题。

且本题只是一个填空题,并不需要多高超的解题方法,在分析实际代码的时间复杂度允许的情况下,我们完全可以选择舍弃一点时间来换取答案的准确性。

那么对于上述代码,其实还可以在某些地方再次进行简化,亲爱的小伙伴,请问你看出来了吗?欢迎在评论区交流哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/69684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于STM32的智能停车场管理系统设计

目录 引言系统设计 硬件设计软件设计 系统功能模块 车辆识别与进出管理模块车位检测与引导模块计费与支付模块数据存储与查询模块远程监控与异常报警模块 控制算法 车牌识别与车辆进出管理算法车位检测与引导算法计费与支付处理算法数据存储与远程反馈算法 代码实现 车辆检测与…

单细胞-第五节 多样本数据分析,打分R包AUCell

文件在单细胞\5_GC_py\1_single_cell\3.AUCell.Rmd 1.基因 rm(list = ls()) load("g.Rdata")2.AUCell https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9897923 IF: NA NA NA用这个文章里的方法,将单细胞亚群的marker基因与ros相关基因取交集,用作AUCell的基因集…

蓝牙技术在物联网中的应用有哪些

蓝牙技术凭借低功耗、低成本和易于部署的特性&#xff0c;在物联网领域广泛应用&#xff0c;推动了智能家居、工业、医疗、农业等多领域发展。 智能家居&#xff1a;在智能家居系统里&#xff0c;蓝牙技术连接各类设备&#xff0c;像智能门锁、智能灯泡、智能插座、智能窗帘等。…

NLP深度学习 DAY5:Seq2Seq 模型详解

Seq2Seq&#xff08;Sequence-to-Sequence&#xff09;模型是一种用于处理输入和输出均为序列任务的深度学习模型。它最初被设计用于机器翻译&#xff0c;但后来广泛应用于其他任务&#xff0c;如文本摘要、对话系统、语音识别、问答系统等。 核心思想 Seq2Seq 模型的目标是将…

单细胞-第四节 多样本数据分析,下游画图

文件在单细胞\5_GC_py\1_single_cell\2_plots.Rmd 1.细胞数量条形图 rm(list ls()) library(Seurat) load("seu.obj.Rdata")dat as.data.frame(table(Idents(seu.obj))) dat$label paste(dat$Var1,dat$Freq,sep ":") head(dat) library(ggplot2) lib…

NLP模型大对比:Transformer >Seq2Seq > LSTM > RNN > n-gram

结论 Transformer 大于 传统的Seq2Seq 大于 LSTM 大于 RNN 大于 传统的n-gram n-gram VS Transformer 我们可以用一个 图书馆查询 的类比来解释它们的差异&#xff1a; 一、核心差异对比 维度n-gram 模型Transformer工作方式固定窗口的"近视观察员"全局关联的&q…

Julius AI 人工智能数据分析工具介绍

Julius AI 是一款由 Casera Labs 开发的人工智能数据分析工具&#xff0c;旨在通过自然语言交互和强大的算法能力&#xff0c;帮助用户快速分析和可视化复杂数据。这款工具特别适合没有数据科学背景的用户&#xff0c;使数据分析变得简单高效。 核心功能 自然语言交互&#x…

H3CNE-31-BFD

Bidirectional Forwarding Dection&#xff0c;双向转发检查 作用&#xff1a;毫秒级故障检查&#xff0c;通常结合三层协议&#xff08;静态路由、vrrp、ospf、BGP等&#xff09;&#xff0c;实现链路故障快速检查。 BFD配置示例 没有中间的SW&#xff0c;接口down&#xff…

2025最新版MySQL安装使用指南

2025最新版MySQL安装使用指南 The Installation and Usage Guide of the Latest Version of Oracle MySQL in 2025 By JacksonML 1. 获取MySQL 打开Chrome浏览器&#xff0c;访问官网链接&#xff1a;https://www.mysql.com/ &#xff0c;随即打开MySQL官网主页面&#xff…

[前端开发]记录国内快速cdn库,用于在线引入JavaScript第三方库

字节跳动的两个库,官网地址如下,搜索时优先找第一个,可用来链接axios,Boostrap等等第三方库 1. 字节跳动静态资源公共库 比如说搜索lodash,用于节流防抖的库,点击复制即可,一般是****.js或****.min.js这样的为后缀名的链接 点击复制即可, <script src"https://lf9-cd…

【云安全】云原生-K8S-搭建/安装/部署

一、准备3台虚拟机 务必保证3台是同样的操作系统&#xff01; 1、我这里原有1台centos7&#xff0c;为了节省资源和效率&#xff0c;打算通过“创建链接克隆”2台出来 2、克隆之前&#xff0c;先看一下是否存在k8s相关组件&#xff0c;或者docker相关组件 3、卸载原有的docker …

vim交换文件的作用

1.数据恢复&#xff1a;因为vim异常的退出&#xff0c;使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑&#xff1a;vim检测到交换文件的存在,会给出提示&#xff0c;以避免一个文件同时被多人编辑。 &#xff08;vim交换文件的工作原理&#xff1a;vim交换文件的工作…

【Block总结】OutlookAttention注意力,捕捉细节和局部特征|即插即用

论文信息 标题: VOLO: Vision Outlooker for Visual Recognition作者: Li Yuan, Qibin Hou, Zihang Jiang, Jiashi Feng, Shuicheng Yan代码链接: https://github.com/sail-sg/volo论文链接: https://arxiv.org/pdf/2106.13112 创新点 前景注意力机制: VOLO引入了一种称为“…

【Unity3D】实现横版2D游戏角色二段跳、蹬墙跳、扶墙下滑

目录 一、二段跳、蹬墙跳 二、扶墙下滑 一、二段跳、蹬墙跳 GitHub - prime31/CharacterController2D 下载工程后直接打开demo场景&#xff1a;DemoScene&#xff08;Unity 2019.4.0f1项目环境&#xff09; Player物体上的CharacterController2D&#xff0c;Mask添加Wall层…

premierePro 2022创建序列方式

概念 序列类似于画画的画布&#xff0c;类似ps的蒙层 一、新建序列方式 1、文件-新建-序列 2、直接将视频拖入时间轴&#xff08;没有序列时&#xff0c;如果有序列不行&#xff09; 3、右键右下角空白处 4、点击新建项按钮

九大服务构建高效 AIOps 平台,全面解决GenAI落地挑战

最近,DevOps运动的联合创始人Patrick Debois分享了他对AI平台与软件研发关系的深刻见解,让我们一起来探讨这个话题。 在AI的落地过程中,我们面临着两个主要难题: 引入AI编码工具后的开发者角色转变:随着像GitHub Copilot这样的AI工具的普及,工程师的角色正在发生深刻变革…

Golang :用Redis构建高效灵活的应用程序

在当前的应用程序开发中&#xff0c;高效的数据存储和检索的必要性已经变得至关重要。Redis是一个快速的、开源的、内存中的数据结构存储&#xff0c;为各种应用场景提供了可靠的解决方案。在这个完整的指南中&#xff0c;我们将学习什么是Redis&#xff0c;通过Docker Compose…

对顾客行为的数据分析:融入2+1链动模式、AI智能名片与S2B2C商城小程序的新视角

摘要&#xff1a;随着互联网技术的飞速发展&#xff0c;企业与顾客之间的交互方式变得日益多样化&#xff0c;移动设备、社交媒体、门店、电子商务网站等交互点应运而生。这些交互点不仅为顾客提供了便捷的服务体验&#xff0c;同时也为企业积累了大量的顾客行为数据。本文旨在…

毕业设计--具有车流量检测功能的智能交通灯设计

摘要&#xff1a; 随着21世纪机动车保有量的持续增加&#xff0c;城市交通拥堵已成为一个日益严重的问题。传统的固定绿灯时长方案导致了大量的时间浪费和交通拥堵。为解决这一问题&#xff0c;本文设计了一款智能交通灯系统&#xff0c;利用车流量检测功能和先进的算法实现了…

算法题(51):删除链表的倒数第N个节点

审题&#xff1a; 需要我们找到倒数第n个节点&#xff0c;并把他从链表中删除&#xff0c;然后把新的链表的头结点返回 思路&#xff1a; 该题的唯一难点就是如何找到单链表的倒数第n个节点 方法一&#xff1a;直接法 我们可以遍历一次单链表&#xff0c;然后把链表的总长度求出…