写给妹妹的编程札记 6 - 搜索实战: 单词博弈

2024-05-24 04:58

本文主要是介绍写给妹妹的编程札记 6 - 搜索实战: 单词博弈,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        最近,CSDN上的在线编程比赛中,有一道题目《单词博弈》。这道题目是一个很好的可以使用搜索来解决的例子。

题目详情
本第一次在线编程大赛由文思海辉冠名,题目如下:
甲乙两个人用一个英语单词玩游戏。两个人轮流进行,每个人每次从中删掉任意一个字母,如果剩余的字母序列是严格单调递增的(按字典序a < b < c <....<z),则这个人胜利。两个人都足够聪明(即如果有赢的方案,都不会选输的方案 ),甲先开始,问他能赢么?
输入: 一连串英文小写字母,长度不超过15,保证最开始的状态不是一个严格单增的序列。
输出:1表示甲可以赢,0表示甲不能赢。
例如: 输入 bad, 则甲可以删掉b或者a,剩余的是ad或者bd,他就赢了,输出1。
又如: 输入 aaa, 则甲只能删掉1个a,乙删掉一个a,剩余1个a,乙获胜,输出0。

        有别于以往的搜索问题,在这个问题中,有两个角色,为游戏双方 - 甲和乙。 但,需要说明的是, 这个题目比平常的围棋象棋简单很多,一个是问题的搜索空间很小, 另一个是任意一个状态对甲乙来说是一样的。比如,甲乙面对字符串bad的输赢情况是一样的。 不难看出,对于一个状态,要么先玩的选手赢,要么先玩的选手输,只有这两种可能。题目已经假设最开始的状态不是一个严格单增的序列,并且如果一个选手通过删除掉一个字母到达一个单增序列的话就可以胜利。那么,我们有下面的发现:

        1. 如果一个字母序列X是单增的, 那么,对于这个序列来说, 先行者输

        2. 否则,一个长度为k的字母序列X, 删除一个字母后, 可以得到k个字母序列(删除其中的第1,2,...,k个字母分别得到字母序列Y_1, Y_2, ..., Y_k )

            a. 如果这k个字母序列中任意一个是先行者输, 那么X是先行者赢

                为什么? 假设Y_i是先行者输,那么对于字母序列X来说, 作为先行者,我们只要删除字母i,到达Y_i, 下一个选手就输了。

            b. 如果这k个字母序列中任意一个都不是先行者输, 也就是说所有k个字母序列都是先行者赢, 那么X是先行者输

                为什么? 因为对于X这个字母序列来说, 不管我们删除哪个字母,到达的k个字母序列都是先行者赢。 也就是下一个选手会赢。


        有了上面的这些观察, 让我们来看看整棵博弈搜索树是怎样的? 拿“bdca”为例吧 (图中W表示先行者赢, L表示先行者输)


        对于字母序列"bdca",首先搜索第一个分支"_dca",当搜索完分支"_dca"之后,发现"_dca"是一个先行者输的状态,也就是说我们如果删除字母b,到达字母序列"_dca"的话,下一个选手不管怎么走都是输,也就是说"bdca"是先行者赢的,我们的取胜策略是删除第一个字母。 后面的三个分支没有必要计算了。

        到这里为止,应该不难写出一个搜索程序来了。需要注意的地方是, 像往常一样, 搜索树上有很多节点是重复的,比如上面"___a", "__c_"等。 搜索时候如何避免这些重复状态将直接影响整个程序的效率。

        考虑下,搜索树中可能的字母序列数目是多少呢? 如果输入字母序列长度为k, 那么可能的字母序列为2^k  (序列中每个字母都可能被删除了或者还没有被删除)。 题目中限制输入的字母序列长度不超过15,所以搜索树中可能的字母序列不超过2^15, 是一个很小的数目。比如,"bdca"可以用15表示 (2^4 - 1), 4个bit, 每个bit都是1. “_dca”可以用14表示,除了第一个bit其他bit都是1. 我们可以申请一个空间v[], v[i]记录i对应的字母序列是先行者赢还是先行者输。 搜索的伪代码如下:

char search(int state)
{// 检查state是否已经计算过,避免重复计算if (v[state] != '') {return v[state];}// 检查state是否是单增序列, 如果是单增序列,返回"L"if (单增序列) {return 'L';}// 尝试删除每一个字母,看是否能找到先行者输的字母序列for (int i = 0; i < k; i++) {next_state = 删除字母i之后得到的新的字母序列if (search(next_state) == 'L') return 'W';}return 'L';
}

        由于搜索的整个空间很少, 下面是一个遍历所有可能字母序列的程序, 没有按上面伪代码这样编写常规的递归搜索程序。 一个不足的地方在于,可能计算了一些字母序列,但这些字母序列根本没有必要计算。 比如上图中,经过剪枝, 需要计算的字母序列数目非常少。好处当然是图方便编写简单。 至于,为什么从小到大开始计算呢? 因为每一个字母序列x,删除其中任意一个字母后得到新的字母序列y, 那么x对应的整数一定比y对应的整数大。 从小到大遍历一遍所有可能的字母序列可以保证在计算x对应的字母序列时,该字母序列通过删除字母得到的字母序列肯定都已经计算过了。

int who(const char* word)
{int   n = strlen(word);int   nState = (1<<n);int   k, nextK, i;char* v = (char*)malloc(sizeof(char) * nState);memset(v, 0, sizeof(char) * nState);for(k = 1; k < nState; k++) {// 检查是否单增字母序列int isFinished = 1;char prevChar = 'a' - 1;for(i = 0; i < n; i++) {if ((k & (1<<i)) > 0) {if (word[i] <= prevChar) {isFinished = 0; break;} else {prevChar = word[i];}}}// 1. 如果单增序列 - 先行者输if (isFinished) {v[k] = 'L';}else{// 2. 如果不是单增序列,检查可能的子节点,当找到先行者输节点时可以提前终止int canWin = 0;for(i = 0; i < n; i++) {if ((k & (1<<i)) > 0) {nextK = k ^ (1<<i);if (v[nextK] == 'L') { canWin = 1; break;}}}if (canWin) v[k] = 'W'; else v[k] = 'L';}}int win = (v[nState - 1] == 'W') ? 1 : 0;free(v);if (win) return 1; else return 0;
}


这篇关于写给妹妹的编程札记 6 - 搜索实战: 单词博弈的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/997441

相关文章

在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程

《在Java中使用ModelMapper简化Shapefile属性转JavaBean实战过程》本文介绍了在Java中使用ModelMapper库简化Shapefile属性转JavaBean的过程,对比... 目录前言一、原始的处理办法1、使用Set方法来转换2、使用构造方法转换二、基于ModelMapper

Java实战之自助进行多张图片合成拼接

《Java实战之自助进行多张图片合成拼接》在当今数字化时代,图像处理技术在各个领域都发挥着至关重要的作用,本文为大家详细介绍了如何使用Java实现多张图片合成拼接,需要的可以了解下... 目录前言一、图片合成需求描述二、图片合成设计与实现1、编程语言2、基础数据准备3、图片合成流程4、图片合成实现三、总结前

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

nginx-rtmp-module构建流媒体直播服务器实战指南

《nginx-rtmp-module构建流媒体直播服务器实战指南》本文主要介绍了nginx-rtmp-module构建流媒体直播服务器实战指南,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有... 目录1. RTMP协议介绍与应用RTMP协议的原理RTMP协议的应用RTMP与现代流媒体技术的关系2

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

Golang操作DuckDB实战案例分享

《Golang操作DuckDB实战案例分享》DuckDB是一个嵌入式SQL数据库引擎,它与众所周知的SQLite非常相似,但它是为olap风格的工作负载设计的,DuckDB支持各种数据类型和SQL特性... 目录DuckDB的主要优点环境准备初始化表和数据查询单行或多行错误处理和事务完整代码最后总结Duck

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

Golang使用minio替代文件系统的实战教程

《Golang使用minio替代文件系统的实战教程》本文讨论项目开发中直接文件系统的限制或不足,接着介绍Minio对象存储的优势,同时给出Golang的实际示例代码,包括初始化客户端、读取minio对... 目录文件系统 vs Minio文件系统不足:对象存储:miniogolang连接Minio配置Min

Node.js 中 http 模块的深度剖析与实战应用小结

《Node.js中http模块的深度剖析与实战应用小结》本文详细介绍了Node.js中的http模块,从创建HTTP服务器、处理请求与响应,到获取请求参数,每个环节都通过代码示例进行解析,旨在帮... 目录Node.js 中 http 模块的深度剖析与实战应用一、引言二、创建 HTTP 服务器:基石搭建(一