首页 > 其他分享 >【动态规划】博弈问题

【动态规划】博弈问题

时间:2023-01-12 17:44:35浏览次数:51  
标签:分数 博弈 nums 玩家 second 动态 规划 dp first

目录

博弈问题

具有竞争或对抗性质的行为称为博弈行为,在这类行为中,参加斗争或竞争的各方各自具有不同的目标或利益。博弈论是二人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜的目的。

为了达到各自的目标和利益,各方必须考虑对手的各种可能的行动方案,并力图选取对自己最为有利或最为合理的方案,比如日常生活中的下棋,打牌等。博弈论就是研究博弈行为中斗争各方是否存在着最合理的行为方案,以及如何找到这个合理的行为方案的数学理论和方法。

应用

应用1:Leetcode.486

题目

486. 预测赢家

方法一:动态规划(自底向上)

解题思路

设 \(dp[i][j]\) 表示从子数组 \(nums[i], \cdots, nums[j]\) 中做选择,当前玩家与另一个玩家的分数差值的最大值

同时,我们假设数组 \(nums\) 的长度为 \(n\) ,因为两位选手是轮流选择的,我们只关心最终的状态,也就是 \(dp[0[n - 1]\) 的状态,所以,我们只需要通过状态转移记录每次选择的方案就行了。

因此,当所有的状态枚举完了之后,如果 \(dp[0[n - 1]\) 的值大于零,那么当前玩家(先手的玩家)的分数大于另一个玩家的得分,则先手赢,否则,后手赢。

边界条件

显然,\(i\), \(j\)需要满足:

\[\begin{aligned} &0 \le i \le n - 1 \\ &0 \le j \le n - 1 \end{aligned} \]

并且,当 \(i = j\) 时,即子数组的长度为 \(1\) 时,当前玩家只能选择这个数字,两人的分数差值的最大值就是当前的数字,即

\[dp[i][i] = nums[i], \quad 0 \le i \le n -1 \]

也就是说,如果只有一个数字,先手的玩家始终会成为赢家。

状态转移

假设子数组中的元素大于等于两个时,那么,状态 \(dp[i][j]\) 对应的子数组中的元素可以表示为:

\[nums[i], \ nums[i + 1], \ \cdots, \ nums[j - 1], \ nums[j] \]

此时,对于当前玩家,他有两种选择,他可以选择 \(nums[i]\) 或者 \(nums[j]\) :

  • 如果当前玩家选择 \(nums[i]\),那么,当前两者分数的差值,就是当前选择的数字 \(nums[i]\) 减去上一个状态的差值,即 \(nums[i] - dp[i + 1][j]\);
  • 如果当前玩家选择 \(nums[j]\),那么,当前两者分数的差值,就是当前选择的数字 \(nums[j]\) 减去上一个状态的差值,即 \(nums[j] - dp[i][j - 1]\);

在两种方案中,当前玩家一定会选择最优的方案,使得自己的分数最大化,即上述两种选择之中的最大值,因此,状态转移方程:

\[dp[i][j] = \max(nums[i] - dp[i + 1][j], \ nums[j] - dp[i][j - 1]), \quad i \lt j \]

这里,我们不用考虑某个位置是谁在做选择,因为每个位置,对应的玩家都会做最优选择。

我们只需要考虑最终状态状态 \(dp[0[n - 1]\) 就行了,因为只有先手的玩家会对应状态 \(dp[0[n - 1]\)。

代码实现

class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        n = len(nums)
        dp = [ [0] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = nums[i]
        # 枚举所有的状态
        for i in range(n - 2, -1, -1):
            for j in range(i + 1, n):
                dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])
        return dp[0][n - 1] >= 0

方法二:动态规划(自顶向下)

解题思路

这里我们利用分治的思想:通过定义一个带返回值的递归函数,将问题分解为子问题(子树),通过递归推导出答案。

这里,我们定义一个play(nums, left, right)函数,表示当从子数组 \(nums[left]\) , \(\cdots\), \(nums[right]\) 中做选择时,当前玩家与另一个玩家的分数差值的最大值。

递归的终止条件就是子数组长度为零时,即 \(left \gt right\),两者分数的差值为零。

代码实现

class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        return self.play(nums, 0, len(nums) - 1) >= 0

    def play(self, nums, left, right):
        if left > right:
            return 0

        plan_a = nums[left] - self.play(nums, left + 1, right)
        plan_b = nums[right] - self.play(nums, left, right - 1)

        return max(plan_a, plan_b)

方法三:动态规划

解题思路

这里我们定一个了一个对象 \(Score\),它有两个属性\(first\)、\(second\),分别表示先手和后手玩家的分数,用于辅助记录状态转移过程。

我们用令 \(dp[i][j]\) 表示从子数组 \(nums[i] , \cdots, nums[j]\) 中做选择,两个玩家可以获取的分数的最大值,即:

  • \(dp[i][j].first\) 表示先手玩家的最大分数;
  • \(dp[i][j].second\) 表示后手玩家的最大分数。

因此,当所有的状态枚举完了之后,如果 \(dp[0[n - 1].first\) 的值大于\(dp[0[n - 1].second\),则先手赢,否则,后手赢。

边界条件

显然,\(i\), \(j\)需要满足:

\[\begin{aligned} &0 \le i \le n - 1 \\ &0 \le j \le n - 1 \end{aligned} \]

并且,当 \(i = j\) 时,即子数组的长度为 \(1\) 时,先手玩家只能选择这个数字,那么先手玩家的分数就是\(nums[i]\),后手玩家的分数就是零,即

\[dp[i][i] = Score(nums[i], 0), \quad 0 \le i \le n -1 \]

也就是说,如果只有一个数字,先手的玩家始终会成为赢家。

状态转移

假设子数组中的元素大于等于两个时,那么,状态 \(dp[i][j]\) 对应的子数组中的元素可以表示为:

\[nums[i], \ nums[i + 1], \ \cdots, \ nums[j - 1], \ nums[j] \]

此时,对于这一轮选择,当前玩家就是先手玩家,他有两种选择,他可以选择 \(nums[i]\) 或者 \(nums[j]\) :

  • 如果当前玩家选择 \(nums[i]\),那么:

    • 他可以获取的大分数 \(y_1\),就是上一个状态的他分数 \(dp[i + 1][j].second\) 加上当前的数字 \(nums[i]\) ,即 \(y_1 = nums[i] + dp[i + 1][j].second\);
    • 另一个玩家的分数保持不变,即还是上一个状态的分数 \(dp[i + 1][j].first\) 。
  • 如果当前玩家选择 \(nums[j]\),那么:

    • 他可以获取的大分数 \(y_2\),就是上一个状态的他分数 \(dp[i][j - 1].second\) 加上当前的数字 \(nums[i]\) ,即 \(y_2 = nums[i] + dp[i][j - 1].second\);
    • 另一个玩家的分数保持不变,即还是上一个状态的分数 \(dp[i][j - 1].first\)。

因此,对于当前的这一轮选择,当前玩家一定会选择对自己有利的方案,即选择 \(y_1\) 和 \(y_2\) 较大的方案,那么

\[dp[i][j]=\begin{cases} Score(nums[i] + dp[i + 1][j].second, \ dp[i + 1][j].first), & y_1 \gt y_2 \\ Score(nums[i] + dp[i][j - 1].second, \ dp[i][j - 1].first), & y_1 \le y_2 \end{cases} \]

注意,对于当前的这一轮选择,当前玩家就是先手玩家,显然,在上一轮他就是后手玩家

代码实现

class Score(object):
    def __init__(self, first: int = 0, second: int = 0):
        self.first = first  # 先手玩家获得的分数
        self.second = second  # 后手玩家获取的分数

    def diff(self):
        return self.first - self.second


class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        n = len(nums)
        dp = [ [Score() for _ in range(n)]  for _ in range(n)]

        for i in range(n):
            dp[i][i] = Score(nums[i], 0)

        for i in range(n - 2, -1, -1):
            for j in range(i + 1, n):
                plan_a = nums[i] + dp[i + 1][j].second
                plan_b = nums[j] + dp[i][j - 1].second
                # 先手玩家选择对其有利的方案,后手玩家的分数不变,还是上一个状态的分数
                if plan_a > plan_b:
                    dp[i][j].first = plan_a
                    dp[i][j].second = dp[i + 1][j].first
                else:
                    dp[i][j].first = plan_b
                    dp[i][j].second = dp[i][j - 1].first

        return dp[0][n - 1].diff() >= 0

标签:分数,博弈,nums,玩家,second,动态,规划,dp,first
From: https://www.cnblogs.com/larry1024/p/17047356.html

相关文章

  • 存储过程动态sql执行
    DELIMITER$$USE`test`$$DROPPROCEDUREIFEXISTS`Prc_TelSuccess_Snapshot_Update`$$CREATEDEFINER=`ccstest`@`%`PROCEDURE`Prc_TelSuccess_Snapshot_Update......
  • 智慧城市规划评估
    一、智慧城市规划的作用和意义是什么?1、规划是建设的基础2、规划关系到建设成败二、智慧城市规划特点1、复杂性2、战略性3、创新性4、系统性5、综合性三、智慧城市规划应该......
  • vue动态挂载组件
    平时我们渲染组件都是通过路由的方式。但有时候不太满足需要,需要我们手动去挂载组件。方式如下:通过调用一个方法去实现动态挂载组件:importVuefrom"vue"importj......
  • 静态Web服务器-命令行启动动态绑定端口号Python解释器详解实现代理池的API模块
    学习目标能够写出获取终端命令行参数动态绑定端口号的web服务器程序1.开发命令行启动动态绑定端口号的静态web服务器实现步骤:获取执行python程序的终端命令行参数判断参数......
  • 0/1分数规划模型
    0/1分数规划模型给定n个物品,每个物品有两个权值ai,bi,从这n个物品选取k(0≤k≤n)个,使得\[\frac{\sum_{i=1}^{k}{a_i}}{\sum_{i=1}^{k}{b_i}}\]最大.对于该类......
  • 我的寒假规划(23年)
    写在前面不知不觉已经返乡一个多月了,同时一月已经过半。现在刚刚考完期末考试,临近过年常常需要出去串门,有些东西还没有开始研究。现在现在这里顶一个flag将寒假计划安排出......
  • vue3之 element-plus的动态图标
    Vue2中使用ElementUI的图标渲染是通过<iclass="el-icon-plus"></i>渲染Vue3中使用ElementPlus图标渲染是通过<el-icon><Plus/></el-icon>渲染所以在使用ElementU......
  • 动态范围控制原理
    DRC介绍开门见山,动态范围的定义就是信号的最大幅值和最小幅值比值的对数(单位dB),动态范围会受到系统中各个环节的影响。例如同样是这段音乐,在一个40dB背景噪声......
  • c++静态库和动态库的添加
    #声明要求的cmake最低版本cmake_minimum_required(VERSION2.8)#声明一个cmake工程project(helloSLAM)#设置编译模式set(CMAKE_BUILD_TYPE"Debug")#共享库add_l......
  • 使用gdb加载动态库的符号表
    参考https://blog.csdn.net/lls2012/article/details/103349511上面的参考文章中介绍了一种加载动态库的符号表的方法,其中最重要的是获取动态库的.text段的起始地址,除......