1. 程式人生 > 實用技巧 >2020天梯賽補題

2020天梯賽補題

已知問題規模為n的前提A,求解一個未知解B。

  • 對於Ai+1,只需要Ai即可求解,不需更前序的狀態。這一模型稱為馬爾科夫模型。對應的推理過程稱為“貪心法”
  • 對於Ai+1,需要所有前序狀態才能完成求解。這一模型稱為高階馬爾科夫模型。對應的推理過程稱為“動態規劃法”

能用動態規劃解決的問題的特點

  • 最優化原理:若問題所包含的子問題的解也是最優的,則稱該問題具有最優子結構;
  • 無後效性:某階段一旦確定,不受該決策後續決策影響,即某狀態只與當前狀態有光;
  • 有重疊子問題:一個子問題在下一階段的決策中可能被多次用到(非必要但是是動態規劃演算法相對於其餘演算法的優勢)。

動態規劃演算法一般思路

  • 劃分階段
    :劃分後的階段一定是良序的;
  • 確定狀態和狀態變數:狀態的選擇要滿足無後效性
  • 寫出狀態轉移方程:根據相鄰兩狀態之間的關係確定狀態轉移方程;
  • 尋找邊界條件:狀態轉移方程的終止條件或邊界條件
    • 動態規劃的三要素為———問題階段;每個階段的狀態;狀態轉移方程;
    • 確定了三要素之後,求解過程可用一個最優決策表描述。最優決策表的“行”表示決策階段;“列”表示問題狀態;表格內容為該問題對應在該階段該裝填下的最優值。

典型示例

  1. 斐波那契數列求解:
def fib(n):
    ans=[0,1]
    for i in range(2,n+1):
        ans.append(ans[-1]+ans[-2])
    return ans[-1]

if __name__=='__main__':
    n=int(input("請輸入所求解的資料位置:"))
    print(fib(n))

2.陣列最大不連續遞增子序列

def MaxChildArrayOrder(nums):
    n=len(nums)

    # ans[i]表示[1,i]序列中包含i在內的最大非連續遞增子序列長度
    ans=[1]*(n) 
    # 儲存結果
    maxNums=1 
    # 若nums[i]>nums[j],ans[i]=max(ans[i],ans[j]+1)
    for i in range(2,n):
        for j in range(1,i):
            if nums[j]<nums[i] and ans[i]<ans[j]+1:
                ans[i]=ans[j]+1
        maxNums= max(ans[i],maxNums)

    return maxNums

if __name__=='__main__':
    nums=[3,1,4,1,5,9,2,6,5]
    aNums=[-1]+nums
    print(MaxChildArrayOrder(aNums))

>>>4

  1. 陣列最大連續子序列和
def MaxChildArraySum(nums):
    n=len(nums)

    # 最大子序列和
    maxSum=nums[0]
    # sums[i]表示包含nums[i]在內的最大子序列和
    sums=[nums[0]]

    for i in range(1,n):
        sums.append(max(nums[i],nums[i]+sums[i-1]))
        maxSum=max(maxSum,sums[i])
    
    return maxSum

if __name__=='__main__':
    nums=[6,-1,3,-4,-6,9,2,-2,5]
    print(MaxChildArraySum(nums))

>>>14
  1. 三角形最小路徑和
    三角形最小路徑和
def minimumTotal(triangle):
    # 二維陣列dp[i][j]表示從頂點到triangle[i][j]的最小路徑
    dp=[[triangle[0][0]]]
    n=len(triangle)
    for i in range(1,n):
        temp=[]
        for j in range(i+1):
            # 第1列的狀態值只由上一行第一列決定
            if j==0:
                temp.append(dp[i-1][0]+triangle[i][0])
            # 最後一列的狀態值只由左上角數的狀態值決定
            elif j==i:
                temp.append(dp[i-1][j-1]+triangle[i][j])
            # 其餘由上方或左上角資料狀態值決定
            else:
                temp.append(max(dp[i-1][j-1],dp[i-1][j])+triangle[i][j])
        dp.append(temp)
    
    return max(dp[n-1])

由於二維陣列中存放的是所有的結果,但我們只需要最後一行的值,故可以考慮用一維陣列代替二維陣列,每個階段,用當前陣列(上一階段的結果)更新求得此階段的結果。
滾動陣列求解:

def minimumTotal(triangle):
    n=len(triangle)
    dp=triangle[[0][0]]+[0]*(n-1)
    for i in range(1,n):
        dp[i]=dp[i-1]+triangle[i][i]
        for j in range(i-1,0,-1):
            dp[j]=min(dp[j-1],dp[j])+triangle[i][j]
        dp[0]=dp[0]+triangle[i][0]
    return min(dp)
  1. 揹包問題
    在N件物品取出若干件放在容量為W的揹包裡,每件物品的體積為W1,W2……Wn(Wi為整數),與之相對應的價值為P1,P2……Pn(Pi為整數),求揹包能夠容納的最大價值。
# 物體的重量列表為weight(0~n+1),價值列表為property(0~n+1)[注意weight和property需要提前擴充處理],揹包空間為room
def PackageMaxValue(weight,property,room):
    length=len(weight)
    # dp[i][j]表示w1-wi物品填充空間為j的揹包時,最大的價值
    # dp的第0行和第0列為0
    dp=[[0]*(room+1)]*(length)
    for i in range(1,length):
        for j in range(1,room+1):
            # 若當前空間可以容納wi,考慮放進wi或者不放wi
            if j>weight[i]:
                dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+property[i])
            # 若當前空間不可容納wi,不放
            else:
                dp[i][j]=dp[i-1][j]
    
    return dp[-1][-1]

滾動陣列空間優化:

# 引數同上
def PackageMaxValue(w,p,v):
    length=len(w)
    # dp[j]表示當前階段(即物品有w1~wi)填充空間為j的揹包時最大價值
    dp=[0]*v
    for i in range(1,len(w)):
        for j in range(v,w[i]-1,-1):
            dp[j]=max(dp[j],dp[j-w[i]]+p[i])
    return dp[-1]

5+. 一和零
一和零
與揹包問題的區別在於,這有兩個容量限制條件——0和1的個數,故相對傳統揹包問題,需要將一維的容量狀態變為二維
滾動陣列:

import numpy as np
def findMaxForm(strs, m, n):
    dp=np.zeros((m+1,n+1),dtype=int)
    for i in range(len(strs)):
        zeros,ones=countZerosAndOnes(strs[i])
        for j in range(m,zeros-1,-1):
            for k in range(n,ones-1,-1):
                dp[j][k]=max(dp[j][k],dp[j-zeros][k-ones]+1)
    return dp[m][n]