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.rhkb.cn/news/9922.html

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

相关文章

Origami Agents:AI驱动的销售研究工具,助力B2B销售团队高效增长

在竞争激烈的B2B市场中,销售团队面临着巨大的挑战——如何高效地发现潜在客户并进行精准的外展活动。Origami Agents通过其创新的AI驱动研究工具,正在彻底改变这一过程。本文将深入探讨Origami Agents的产品特性、技术架构及其快速增长背后的成功因素。 一、一句话定位 Ori…

Java---猜数字游戏

本篇文章所实现的是Java经典的猜数字游戏 , 运用简单代码来实现基本功能 目录 一.题目要求 二.游戏准备 三.代码实现 一.题目要求 随机生成一个1-100之间的整数(可以自己设置区间&#xff09;&#xff0c;提示用户猜测&#xff0c;猜大提示"猜大了"&#xff0c;…

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

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

数据结构 队列

目录 前言 一&#xff0c;队列的基本知识 二&#xff0c;用数组实现队列 三&#xff0c;用链表实现队列 总结 前言 接下来我们将学习队列的知识&#xff0c;这会让我们了解队列的基本概念和基本的功能 一&#xff0c;队列的基本知识 (Queue) 我们先来研究队列的ADT&#xff0c…

Git 版本控制:基础介绍与常用操作

目录 Git 的基本概念 Git 安装与配置 Git 常用命令与操作 1. 初始化本地仓库 2. 版本控制工作流程 3. 分支管理 4. 解决冲突 5. 回退和撤销 6. 查看提交日志 前言 在软件开发过程中&#xff0c;开发者常常需要在现有程序的基础上进行修改和扩展。但如果不加以管理&am…

Java 大视界 -- Java 大数据在量子通信安全中的应用探索(69)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

国产碳化硅(SiC)MOSFET模块在电镀电源中全面取代进口IGBT模块

国产碳化硅&#xff08;SiC&#xff09;MOSFET模块在电镀电源中全面取代进口IGBT模块&#xff0c;倾佳电子杨茜分析以下几方面的技术、经济和政策优势&#xff1a; 倾佳电子杨茜致力于推动SiC碳化硅模块在电力电子应用中全面取代IGBT模块&#xff0c;助力电力电子行业自主可控…

linux用户管理

创建用户&#xff1a;useradd &#xff08;创建用户命令的详细使用&#xff1a;如何创建用户-CSDN博客&#xff09; &#xff08;如何创建具有重复uid的用户&#xff1a;如何创建具有重复uid的用户-CSDN博客&#xff09; 删除用户&#xff1a;userdel &#xff08;删除用户命…

【C++动态规划 离散化】1626. 无矛盾的最佳球队|2027

本文涉及知识点 C动态规划 离散化 LeetCode1626. 无矛盾的最佳球队 假设你是球队的经理。对于即将到来的锦标赛&#xff0c;你想组合一支总体得分最高的球队。球队的得分是球队中所有球员的分数 总和 。 然而&#xff0c;球队中的矛盾会限制球员的发挥&#xff0c;所以必须选…

【安全测试】测开方向学习遇到的问题记录

【问题一】springboot如何访问静态资源文件 springboot启动根路径位置 F:\untitled05\demo4\src\main\resources\static 例如图片位置存放在F:\untitled05\demo4\src\main\resources\static即可 配置文件配置 spring.web.resources.static-locationsfile:/F:/untitled05/de…

Github 2025-01-25Rust开源项目日报Top10

根据Github Trendings的统计,今日(2025-01-25统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10Python项目1Vue项目1JavaScript项目1Deno: 现代JavaScript和TypeScript运行时 创建周期:2118 天开发语言:Rust, JavaScript协议类型…

详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)

会将java当中的所有的知识点以及相关的题目进行分享&#xff0c;这是其中的第一部分&#xff0c;用红色字体标注出重点&#xff0c;以及加粗的方式进行提醒 目录 一、Java语言概述 1.Java语言简介 2.语言优势 二、main方法 1.Java程序结构组成 2.运行Java程序 3.注释 4.…

HarmonyOS应用开发快速入门

本节内容将帮助开发者学习如何构建一个全新的HarmonyOS应用&#xff0c;学习使用DevEco Studio创建新项目、使用预览器预览页面、了解基础组件如Image、Text等。 文章目录 一、介绍二、创建一个新项目三、页面结构总览四、自定义文本视图五、创建Image组件 一、介绍 根据本教程…

ICSE‘25 LLM Assistance for Memory Safety

不知道从什么时候开始&#xff0c;各大技术社区&#xff0c;技术群聊流行着 “用Rust重写!” &#xff0c;放一张图(笑死… 这不, 随着大模型技术的流行&#xff0c;大家都在探索如何让大模型自动完成仓库级别(全程序)的代码重构&#xff0c;代码变换&#xff08;Refactor&…

Java实现.env文件读取敏感数据

文章目录 1.common-env-starter模块1.目录结构2.DotenvEnvironmentPostProcessor.java 在${xxx}解析之前执行&#xff0c;提前读取配置3.EnvProperties.java 这里的path只是为了代码提示4.EnvAutoConfiguration.java Env模块自动配置类5.spring.factories 自动配置和注册Enviro…

基于Python的人工智能患者风险评估预测模型构建与应用研究(下)

3.3 模型选择与训练 3.3.1 常见预测模型介绍 在构建患者风险评估模型时,选择合适的预测模型至关重要。不同的模型具有各自的优缺点和适用场景,需要根据医疗数据的特点、风险评估的目标以及计算资源等因素进行综合考虑。以下详细介绍几种常见的预测模型。 逻辑回归(Logisti…

零基础Vue入门4——Vue3基础核心

本节重点&#xff1a; vue3最佳实践 ref reactive computed watch、watchEffect 讲解重点之后下面会带大家开发一个页面&#xff08;表单表格&#xff09;&#xff0c;之后会有一个TodoList的小练习&#xff0c;文末附有小练习的代码参考。跟着练习一定带你可以上手开发vu…

一文掌握ADB的安装及使用

文章目录 一、什么是ADB&#xff1f;二、 安装ADB2.1 下载ADB2.2 配置环境变量 三、连接Android设备四、 常用ADB命令五、ADB高级功能5.1 屏幕截图和录制5.2 模拟按键输入5.3 文件管理5.4 系统设置管理5.5 系统操作指令5.6 日志操作指令5.7 APK操作指令5.8 设备重启和恢复 六、…

使用Edu邮箱申请一年免费的.me域名

所需材料&#xff1a;公立Edu教育邮箱一枚&#xff08;P.S&#xff1a;该服务不支持所有的Edu教育邮箱&#xff0c;仅支持比较知名的院校&#xff09; 说到域名&#xff0c;.me这个后缀可谓是个性十足&#xff0c;适合个人网站、博客等。.me是黑山的国家顶级域名&#xff08;c…

7.抽象工厂(Abstract Factory)

抽象工厂与工厂方法极其类似&#xff0c;都是绕开new的&#xff0c;但是有些许不同。 动机 在软件系统中&#xff0c;经常面临着“一系列相互依赖的对象”的创建工作&#xff1b;同时&#xff0c;由于需求的变化&#xff0c;往往存在更多系列对象的创建工作。 假设案例 假设…