Python动态规划算法笔记

'''动态规划算法
    动态规划(Dynamic Programming,DP) 是运筹学的一个分支,
是求解决策过程最优化 的过程。我们后面说动态规划就可以简称为 DP 算法。
这个算法是在20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决
策过程的优化问题时,提出了著名的最优化原理,从而创立出来的。
动态规划的应用极其广泛,包括工程技术、 经济、工业生产、军事以及自动化控制等领域,
并在背包问题、生产经营问题、资金管理问题、 资源分配问题、最短路径问题和复杂系统可靠
性问题等中取得了显著的效果。
'''
import numpy as np
'''
算法思想:
    1.通过记住已经求解过的中间结果逐步逼近最终结果
    2.分治的思想
'''
# +++++++++++++++++++++++++++++++++++++++++
''' 
步骤:
    1.定义一个列表
    2.确定初始状态
    3.推导状态转移方程
'''
# +++++++++++++++++++++++++++++++++++++++++

# 例题1
'''
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。 
到达 n 阶楼梯你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
'''
def stairs(n):
    '''
    :param n: 楼梯级数
    :return:  可能方案的种数
    '''
    dp = [1,2]
    for i in range(2,n+1):
        dp.append(dp[i-1] + dp[i-2])
    print(dp[n-1])

# 【例题 2】
# 猴子吃桃 猴子第一天采摘了一些桃子,
# 第二天吃了第一天的一半多一个,
# 第三天吃了第二天的一 半多一个...
# 直到第十天就剩下一个 求:第一天的时候一共摘得多少桃?

def monkey():
    dp = [0]*11
    dp[10] = 1
    for i in range(9,0,-1):
        dp[i] = (dp[i+1] + 1) * 2
    print(dp)
    print(dp[1])

# 【例题 3】求斐波那契数列
# 求斐波那契数列的第 20 项是多少?
# 【斐波那契数列:1,1,2,3,5,8,……】。

def fib(n):
    fib_lis = [1,1]
    for i in range(2,n):
        fib_lis.append(fib_lis[i-1] + fib_lis[i-2])
        print(fib_lis)
    print(fib_lis[n-1])


'''例题4
有n个人围成一个圈,按顺序排好号。
然后从第一个人开始报数(从1到3报数),
报到3的人退出圈子,然后继续从1到3报数,
直到最后留下一个人游戏结束,问最后留下的是原来第几号。

输入描述:输入一个正整数n
输出描述:输出最后留下的是原来的第几号
样例输入:5
样例输出:4'''

def out():
    n = int(input())
    dp = [i for i in range(1, n + 1)]
    index = 2
    while len(dp) > 1:
        dp.pop(index)
        index += 2
        index %= len(dp) # 当索引超出范围时
    print(dp[0])

# 例题5
'''
题目描述:
某种病毒具有很强的繁殖能力,从病毒粒子出生后的第5分钟开始,
每分钟可以复制出一个新的病毒粒子。新出生的病毒粒子从第5分钟开始,
也可以每分钟复制一个新的病毒粒子。
举例来说,第1分钟时有一个病毒粒子,
此病毒粒子从第5分钟开始复制新的病毒粒子,
因此第5分钟时的病毒数量为2个;第6分钟时又复制出新的病毒粒子,
因此第6分钟的病毒数量为3个;以此类推,第7分钟时病毒粒子数为4;
第8分钟时病毒粒子数为5;第9分钟时,第5分钟复制出的病毒粒子开始复制新的病毒粒子,
因此第9分钟时的病毒总数为7;第10分钟时,第6分钟复制出的病毒粒子开始复制新的病毒粒子,
因此第10分钟时的病毒粒子总数为10。

编程实现:
计算病毒粒子总数,已知第一分钟时出生了一个病毒粒子,
假设所有病毒粒子不会自动死亡,请计算第N分钟时的病毒粒子总数。
例如:前10分钟病毒粒子的总数分别为1,1,1,1,2,3,4,5,7,10。
输入描述
输入正整数N(0<N<60)表示时间
输出描述
输出第N分钟时,病毒粒子的总数'''

def virus():
    dp = [1, 1, 1, 1, 2, 3, 4, 5, 7, 10]
    n = int(input())
    if n <= 10:
        print(dp[n - 1])
    else:
        for i in range(10, n):
            dp.append(dp[i - 1] + dp[i - 4])
        # print(dp)
        print(dp[n - 1])

# 例题6
'''
【题目描述】
给定一个表示分数的非负整数数组。 
玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,
然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。
直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。

输入一个序列的分数,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
输入输出说明:
输入:一个数字序列,由逗号分隔,代表分数的非负整数数组
输出:玩家1是否会成为赢家,赢为True,输为False
示例 1:
输入:1, 5, 2
输出:False
解释:一开始,玩家1可以从1和2中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和
 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 False 。'''

def game():
    '''贪心算法,无法得到最优解
    每次选择当前能选的最大值'''
    lis = [int(x) for x in input().split(',')]
    player1 = []
    player2 = []
    while len(lis) > 2:
        player1.append(lis.pop(0) if lis[0] > lis[-1] else lis.pop(-1))
        player2.append(lis.pop(0) if lis[0] > lis[-1] else lis.pop(-1))
    if len(lis) == 2:
        m = max(lis)
        player1.append(m)
        lis.remove(m)
        player2.append(lis.pop(0))
    else:player1.append(lis.pop(0))
    # print(player1,player2,lis)
    if sum(player1) > sum(player2):
        print(True)
    else:
        print(False)

def game_dp():
    '''
    1.定义二维数组dp,其行数和列数都等于数组的长度,dp[i][j]表示当数组剩下的部分为下标 i 到下标 j 时,
    当前玩家与另一个玩家的分数之差的最大值,注意当前玩家不一定是先手。
    2.只有当 i≤j 时,数组剩下的部分才有意义,因此当 i>j 时,dp[i][j]=0。
    3.当 i=j 时,只剩一个数字,当前玩家只能拿取这个数字,因此对于所有 0≤i<nums.length,都有 dp[i][i]=nums[i]。
    4.当 i<j 时,当前玩家可以选择nums[i] 或 nums[j],然后轮到另一个玩家在数组剩下的部分选取数字。
    在两种方案中,当前玩家会选择最优的方案,使得自己的分数最大化。因此可以得到如下状态转移方程:
    dp[i][j]=max(nums[i]−dp[i+1][j],nums[j]−dp[i][j−1])
    5.最后判断dp[0][nums.length−1] 的值,如果大于或等于0,则先手得分大于或等于后手得分,因此先手成为赢家,否则后手成为赢家。
    '''
    lis = [int(x) for x in input().split(',')]
    lengh = len(lis)
    dp=np.zeros((lengh,lengh))

    for i in range(lengh):
        dp[i][i] = lis[i]

    for i in range(lengh,-1,-1):  # 这里因为后面的值覆盖前面的值的原因,不能直接range(length)
        for j in range(lengh):
            if i < j :
                dp[i][j] = max((lis[i] - dp[i+1][j]),(lis[j] - dp[i][j-1]))

    print(dp)

    return dp[0][lengh-1] >0




# 例题7
'''
提示信息:
有一个密室逃脱游戏,有100间密室连在一排。密室编号是从1开始连续排列一直排到第100间密室
游戏规则:
1、玩家初始位置在1号密室;
2、每次玩家可以进入右边的一个密室,也可以跳过一个密室进入下个密室
(如:当玩家当前在3号密室,他可以进入4号密室也可以进入5号密室);
3、有毒气的密室不能进入需要避开。
编程实现:
给定三个正整数X,Y,M(1<X<Y<M≤100),表示三个密室编号。
X号密室和Y号密室有毒气泄漏,不能进入,玩家需要进入到M号密室。
按照游戏规则进入M号密室有多少种路线方案。

例如:X=2,Y=4,M=7,2号和4号有毒气泄露,
避开2号和4号毒气密室,进入7号密室有2种路线方案,
分别是1->3->5->6->7路线和1->3->5->7路线。

输入描述:输入三个正整数X,Y,M(1<X<Y≤M),
并以英文逗号隔开
输出描述:输出从1号密室开始避开X、Y号毒气密室,进入M号密室有多少种路线方案'''

def room():
    '''思路:让有毒气的房间置0'''
    x,y,m = [int(x) for x in input().split(',')]
    dp = [0]*(m+1)
    dp[:2] = [0,1]
    for i in range(2,m+1):
        if i == 2 and i != x:
            dp[i] = 1
        elif i == x or i == y:
            dp[i] = 0
        else:
            dp[i] = dp[i-1] + dp[i-2]
    # print(dp)
    print(dp[m])

# 例8
'''一个有名的按摩师会收到源源不断的预约请求,
每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,
因此她不能接受相邻的预约。输入一组数字,用于表示一个预约请求序列,
替按摩师找到最优的预约集合,即总预约时间最长,返回总的分钟数。

输入:1 3 4 5 2
输出:8
解析:选择时间为3和5的预约,总时长3+5=8

输入:2 1 4 5 3 1 1 3
输出:12
解析:选择时间为2、4、3、3的预约,总时长2+4+3+3=12'''

def make_appointment():
    '''关键:
    dp:当前能预约的最大分钟数
    如果'''
    lis = [int(x) for x in input().split()]
    dp = [lis[0]]
    dp.append(max(lis[0], lis[1]))
    lengh = len(lis)
    for i in range(2, lengh):
        dp.append(max(dp[i - 1], dp[i - 2] + lis[i])) # 关键状态转移方程
    # ,sum(lis[:i+1])-dp[i-1])  # 不需要
    # print(dp)
    print(dp[-1])


# 例9
'''你和你的朋友,两个人一起玩捡石头的游戏:
(1) 桌子上有一堆石头,你和你的朋友轮流捡石头,你作为先手
(2) 每一回合,轮到的人最少捡1块石头,最多捡 m 块石头
(3) 捡到最后一块石头的人输掉游戏

假设你们每一步都是最优解,当石头数量为 n 时,请判断你是否可以赢得游戏。
如果可以赢,返回True;否则,返回False。

输入:10 4
输出:True
解析:你4块,朋友1块,你4块,朋友捡走最后一块,你赢得游戏

输入:9 3
输出:False
解析:你2块,朋友2块,你1块,朋友3块,你捡走最后一块,你输掉游戏'''

def stone():
    total,max_num = [int(x) for x in input().split()]
    dp = [0]*(total+1) # 0表示输 1表示赢
    # dp[1] == 0 说明只有一块的时候必输
    for i in range(2,max_num+2):
        dp[i] = 1 # 当剩下2-max_num+1块的时候必赢
    for j in range(max_num + 2,total+1):# 后面的情况会重复前面的基本情况
        dp[j] = dp[j-max_num-1]
    print(bool(dp[total]))




if __name__ == '__main__':
    # for i in range(1,10):
    #     stairs(i)
    # monkey()
    # fib(10)
    # out()
    # virus()
    # game()
    # room()
    # res = game_dp()
    # print(res)
    # make_appointment()
    stone()


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值