動態規劃合集——動態規劃基本原理

動態規劃合集——動態規劃基本原理

  • 動態規劃原理
    • 1258:【例9.2】數字金字塔 動態規劃原理
      • 深度優先搜索
      • 記憶化搜索
      • 動態規劃(順推)
        • 動態規劃原理
        • 題解分析
      • 滾動數組優化
      • 動態規劃(逆推)

動態規劃原理

從數塔問題出發理解動態規劃。

1258:【例9.2】數字金字塔 動態規劃原理

1258:【例9.2】數字金字塔

深度優先搜索

問題要求的是從最高點按照規則走到最低點的路徑的最大的權值和,路徑起點終點固定,走法規則明確,可以考慮用搜索來解決。

定義遞歸函數void Dfs(int x,int y,int Curr),其中(x,y)表示當前已從(1,1)走到(x,y),目前已走路徑上的權值和為Curr。同時用另一個變量Ans記錄最大值。

x=N時,到達遞歸出口,如果CurrAns大,則把Ans更新為Curr
否則向下一行兩個位置行走,即遞歸執行
Dfs(x+1,y,Curr+A[x+1][y])Dfs(x+1,y+1,Curr+A[x+1][y+1])

該方法實際上是把所有路徑都走了一遍,由于每一條路徑都是由N-1步組成,每一步有“左”、“右”兩種選擇,因此路徑總數為 2 N ? 1 2^{N-1} 2N?1
所以該方法的時間復雜度為 O ( 2 N ? 1 ) O(2^{N-1}) O(2N?1),鐵定超時。

#include <iostream>
using namespace std;
int A[1005][1005], F[1005][1005], N, Ans;
void Dfs(int x, int y, int Curr) {if (x == N) {if (Curr > Ans)Ans = Curr;return;}//深度優先搜索是枚舉所有情況,這里只有兩個,而且用形參表示狀態,回溯無難度Dfs(x + 1, y, Curr + A[x + 1][y]);Dfs(x + 1, y + 1, Curr + A[x + 1][y + 1]);
}
int main() {cin >> N;for (int i = 1; i <= N; i++)for (int j = 1; j <= i; j++)cin >> A[i][j];Ans = 0;Dfs(1, 1, A[1][1]);cout << Ans << endl;return 0;
}

記憶化搜索

為了避免重復搜索,我們在dfs的基礎上開設全局數組F[x][y]記錄從(x,y)出發到終點路徑的最大權值和,一開始全部初始化為-1表示未被計算過。

在計算Dfs(x,y)時,首先查詢F[x][y],如果F[x][y]不等于-1,說明Dfs(x,y)之前已經被計算過,直接返回 F[x][y]即可,否則計算出Dfs(x,y)的值并存儲在F[x][y]中。

由于F[x][y]對于每個合法的(x,y)都只計算過一次,
而且計算是在 O ( 1 ) O(1) O(1)內完成的,因此時間復雜度為 O ( N 2 ) O(N^2) O(N2)。可以通過本題。

記憶化搜索的本質已經是動態規劃。因為記憶化搜索也是記錄走過的分支,相當于已經解決了子問題,再遍歷這條路徑時直接由子問題解決當前問題即可。

#include <iostream>
#include <algorithm>
using namespace std;
int A[505][505],
F[505][505],N;
int Dfs(int x,int y) {if (F[x][y]== -1) {if (x==N)F[x][y]=A[x][y];elseF[x][y]=A[x][y]+max(Dfs(x+1,y),Dfs(x+1,y+1));}return F[x][y];
}
int main() {cin >> N;for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)cin >> A[i][j];for(int i = 1;i <= N;i ++)for(int j = 1;j <= i;j ++)F[i][j] = -1;Dfs(1,1);cout << F[1][1] << endl;return 0;
}

動態規劃(順推)

動態規劃原理

動態規劃(Dynamic Programming,簡稱dp)是1951年美國數學家R.Bellman等人提出。具體詳見動態規劃_百度百科。

簡單總結就是

  1. 一個大問題拆分成若干子問題,每個子問題的結果都是一個狀態

  2. 由一個子問題解決另一個子問題選擇的方法稱為決策,在現實解決問題時我們都希望找到最優解,也就是最優決策

  3. 同一個大問題拆分成的子問題之間往往都有一樣的規律。或者說,同一個大問題產生的任意兩個不同狀態之間有相同的聯系。這個聯系一般用數學公式表達,這個公式的專業術語叫狀態轉移方程

  4. 動態規劃滿足兩個條件:

    • 最優化原理:每一步決策都是最好的解決方案,都是從很多方案中選擇最好的方案。
    • 無后效性:每個狀態除了第一個,都只由前階段的狀態和決策決定,和后續無關。
題解分析

1、狀態定義:
題目要求從(1,1)出發到最底層路徑最大權值和,路徑中是各個點串聯而成,路徑起點固定,終點和中間點相對不固定。因此定義F[x][y]表示從(1,1)出發到達(x,y)的路徑最大權值和。
最終答案Ans=max{F[N][1],F[N][2],...,F[N][N]}

2、狀態轉移方程&邊界條件(或初始化方式):
不去考慮(1,1)(x,y)的每一步是如何走的,只考慮最后一步是如何走,根據最后一步是向
左還是向右分成以下兩種情況:
向左:最后一步是從(x-1,y)走到(x,y), 此類路徑被分割成兩部分,

第一部分是從(1,1)走到(x-1,y),第二部分是從(x-1,y)走到(x,y)

第一部分的最大權值和,此部分問題的性質與F[x][y]的定義一樣,就是F[x-1][y]

第二部分就是A[x][y],兩部分相加即得到此類路徑的最大權值和為F[x-1,y]+A[x,y]

向右: 最后一步是從(x-1,y-1)走到(x,y),此類路徑被分割成兩部分,

第一部分是從(1,1)走到(x-1,y-1),第二部分是從(x-1,y-1)走到(x,y),分析方法如上。

此類路徑的最大權值和為
F[x-1,y-1]+A[x,y]

F[x][y]的計算需要求出上面兩種情況的最大值。

綜上,得到狀態轉移方程如下:
F[x][y]=max{F[x-1,y-1],F[x-1][y]}+A[x,y]

與遞歸關系式還需要遞歸終止條件一樣,這里我們需要對邊界進行處理以防止無休止地進行下去。觀察發現計算F[x][y]時需要用到F[x-1][y-1]F[x-1][y],是上一行的元素,隨著遞歸的深入,
最終都要用到第一行的元素F[1][1], F[1][1]的計算不能再使用狀態轉移方程來求,而是應該直接賦予一個特值A[1][1]。這就是邊界條件

綜上得:
狀態轉移方程F[x][y]=max{F[x-1][y-1],F[x-1][y]}+A[x,y]
邊界條件F[1][1]=A[1][1]

分析該動態規劃的正確性,分析該解法是否滿足使用動態規劃的兩個前提。

最優化原理:這個在分析狀態轉移方程時已經分析得比較透徹,明顯是符合最優化原理的。

無后效性:狀態轉移方程中,我們只關心F[x-1][y-1]F[x-1][y]的值,計算F[x-1][y-1]時可能有多種不同的決策對應著最優值,選哪種決策對計算F[x][y]的決策沒有影響,
F[x-1][y]也是一樣。這就是無后效性。

在以后的題目中可能不會提及,但處處都充斥著這兩個前提的分析。

3、填表(或程序實現):
由于狀態轉移方程就是遞歸關系式,邊界條件就是遞歸終止條件,所以可以用遞歸來完成,遞歸存在重復調用,利用記憶化可以解決重復調用的問題,這就是方法二的記憶化搜索

記憶化實現比較簡單,而且不會計算無用狀態,但遞歸也會受到棧的大小遞推加回歸執行方式的約束,另外記憶化實現調用狀態的順序是按照實際需求而展開,沒有大局規劃,不利于進一步優化

所以dp更常用的還是迭代法。狀態用二維數組表示,也就是說可以通過循環的方式求出每個狀態(通俗點稱呼就是填表)。

計算F[x][y]用到狀態F[x-1][y-1]F[x-1][y],這些元素在F[x][y]的上一行,也就是說要計算第x行的狀態的值,必須要先把第x-1行元素的值計算出來,因此我們可以先把第一行元素F[1][1]賦為 A[1][1],再從第二行開始按照行從左到右遞增從上到下遞增的順序通過循環計算出每一行的有效狀態即可。時間復雜度為 O ( N 2 ) O(N^2) O(N2)

參考程序:

#include <iostream>
#include <algorithm>
using namespace std;
int A[1005][1005], F[1005][1005], N;
int main() {cin >> N;for (int i = 1; i <= N; i++)for (int j = 1; j <= i; j++)cin >> A[i][j];F[1][1] = A[1][1];for (int i = 2; i <= N; i++)for (int j = 1; j <= i; j++)F[i][j] = max(F[i - 1][j - 1], F[i - 1][j]) + A[i][j];int ans = 0;for (int i = 1; i <= N; i++)ans = max(ans, F[N][i]);cout << ans << endl;return 0;
}

滾動數組優化

根據之前得到的狀態轉移方程F[x][y]=max{F[x-1][y-1],F[x-1][y]}+A[x,y]
邊界條件F[1][1]=A[1][1]

發現在二維數組表示的轉移方程中,每個狀態都只和上一個狀態有關。而且每個狀態都只和前1個狀態有關,和前2個以及之前的所有狀態都無關。

所以可以將表示狀態的dp表用一維數組表示。狀態轉移方程變為

f[x]=max(f[x],f[x-1])+A[x,y]

這種不改變空間復雜度,但可以極大程度地優化空間復雜度的狀態表示稱作滾動數組優化。

在填表的時候,需要將循環從右往左枚舉,才能保證每個狀態都是正確的。

請添加圖片描述

因為都是正數,所以不必考慮邊界為0時造成的影響。如果存在負數,則可能需要對邊界情況做判斷,或取無窮小。

滾動數組優化參考程序:

#include<bits/stdc++.h>
using namespace std;int main() {int n;cin >> n;vector<vector<int> >A(1, vector<int>(2,0));vector<int>f(n+1,0);for (int i = 1; i <= n; i++) {A.push_back(vector<int>(i + 2, 0));for (int j = 1; j <= i; j++)cin >> A[i][j];}int ans = A[1][1];for (int i = 1; i <= n; i++) {for (int j = i; j >= 1; j--) {//逆向枚舉f[j] = max(f[j], f[j - 1]) + A[i][j];ans = max(ans, f[j]);}}cout << ans;return 0;
}

整體來說滾動數組優化的步驟:

  1. 先用二維或多維解決問題。
  2. 若狀態轉移方程的狀態只和上一層或上幾層格子有關,則可以考慮優化。否則不能優化。
  3. 若判斷出可以優化,則將原來的二維和多維狀態減少適當的維度,并看情況修改循環的枚舉順序。

當然不是所有的題都能做空間優化。

動態規劃(逆推)

這里思路和之前是一樣的,只是狀態的設定不同。

以這個樣例為例:

5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11

自底向上計算:(給出遞推式和終止條件)

從底層開始,本身數即為最大數;

倒數第二層的計算,取決于底層的數據,每次都取最大:
12 + 6 = 18 , 13 + 14 = 27 , 24 + 15 = 39 , 24 + 8 = 32 12+6=18,13+14=27,24+15=39,24+8=32 12+6=1813+14=2724+15=3924+8=32
倒數第三層的計算,取決于底二層計算的數據,每次都取最大:
27 + 12 = 39 , 39 + 7 = 46 , 39 + 26 = 65 27+12=39,39+7=46,39+26=65 27+12=3939+7=4639+26=65
倒數第四層的計算,取決于底三層計算的數據,每次都取最大:
46 + 11 = 57 , 65 + 8 = 73 46+11=57,65+8=73 46+11=5765+8=73
最后的路徑:
5—>13—>8->26—>15—>24

這是手搓簡單情況,復雜情況人的效率遠遠不及計算機,需要交給計算機來做。

數據結構及算法設計

圖形轉化:直角三角形,便于搜索:向下、向右。

用三維數組表示數塔:

a[x][y][1]表示行、列及結點本身數據,
a[x][y][2]能夠取得最大值,
a[x][y][3]表示前進的方向,0表示向下,1表示向右。

算法實現

數組初始化,輸入每個結點值及初始的最大路徑、前進方向為0;從倒數第二層開始向上一層求最大路徑,共循環N-1次;從頂向下,輸出路徑:究竟向下還是向右取決于列的值,若列的值比原先多則向右,否則向下。

#include<iostream>
using namespace std;
int main()
{int n, x, y;int a[1001][1001][4]={0};cin >> n;for (x = 1; x <= n; x++)//輸入數塔的初始值for (y = 1; y <= x; y++) {cin >> a[x][y][1];//1表示值a[x][y][2] = a[x][y][1];//2表示當前狀態,未經計算時默認是原來的數a[x][y][3] = 0;//3表示路徑走向,默認向下}for (x = n - 1; x >= 1; x--)//從倒數第二行開始推 for (y = 1; y <= x; y++)if (a[x + 1][y][2] > a[x + 1][y + 1][2]) {//選擇最大路徑值a[x][y][2] = a[x][y][2] + a[x + 1][y][2];a[x][y][3] = 0;}else {a[x][y][2] = a[x][y][2] + a[x + 1][y + 1][2];a[x][y][3] = 1;}cout << a[1][1][2] << endl;//輸出數塔最大值枚舉路徑,這題可忽略//y = 1;//for (x = 1; x <= n - 1; x++){//輸出數塔最大值的路徑//	cout << a[x][y][1] << "->";//	y = y + a[x][y][3];//下一行的列數//}//cout << a[n][y][1] << endl;return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898364.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898364.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898364.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何讓節卡機器人精準對點?

如何讓節卡機器人精準對點&#xff1f; JAKA Zu 軟件主界面主要由功能欄、開關欄、菜單欄構成。 菜單欄&#xff1a;控制柜管理&#xff0c;機器人管理與軟件管理組成。主要功能為對控制柜關機、APP 設置、機器人本體設 置、控制柜設置、連接機器人和機器人顯示等功能。 開關…

自動化測試工具-Playwright介紹和快速實例

Playwright 是什么 Playwright 是由 Microsoft 開發的開源自動化測試工具,專為現代 Web 應用設計。它支持 Chromium、Firefox 和 WebKit 內核的瀏覽器,能夠跨平臺(Windows、macOS、Linux)運行,提供強大的瀏覽器自動化能力,適用于測試、爬蟲和監控等場景。 Playwright的…

軟考程序員考試知識點匯總

軟考程序員考試&#xff08;初級資格&#xff09;主要考察計算機基礎理論、編程能力及軟件開發相關知識。以下是核心知識點總結及備考建議&#xff1a; 一、計算機基礎 數制與編碼 二進制、八進制、十進制、十六進制轉換原碼、反碼、補碼表示&#xff08;整數與浮點數&#xf…

實時視頻分析的破局之道:藍耘 MaaS 如何與海螺 AI 視頻實現高效協同

一、藍耘 MaaS 平臺&#xff1a;AI 模型全生命周期管理的智能引擎 藍耘 MaaS&#xff08;Model-as-a-Service&#xff09;平臺是由藍耘科技推出的 AI 模型全生命周期管理平臺&#xff0c;專注于為企業和開發者提供從模型訓練、推理到部署的一站式解決方案。依托云原生架構、高…

設計模式(行為型)-策略模式

目錄 定義 類圖 角色 角色詳解 Strategy&#xff08;抽象策略類&#xff09;? Context&#xff08;環境類 / 上下文類&#xff09;? ConcreteStrategy&#xff08;具體策略類&#xff09;? 優缺點 優點? 缺點? 使用場景 類行為差異場景? 動態算法選…

【算法day14】三數之和

三數之和 https://leetcode.cn/problems/3sum/description/ 給你一個整數數組 nums &#xff0c;判斷是否存在三元組 [nums[i], nums[j], nums[k]] 滿足 i ! j、i ! k 且 j ! k &#xff0c;同時還滿足 nums[i] nums[j] nums[k] 0 。請你返回所有和為 0 且不重復的三元組。…

優化器/模型參數/超參數

參數&#xff08;Parameters&#xff09; vs. 超參數&#xff08;Hyperparameters&#xff09; 1.1 參數&#xff08;Parameters&#xff09; 定義&#xff1a;模型中需要學習的變量&#xff0c;例如神經網絡中的權重&#xff08;Weight&#xff09;和偏置&#xff08;Bias&a…

10、STL中的unordered_map使用方法

一、了解 1、unordered_map(哈希) unordered_map是借用哈希表實現的關聯容器。 訪問鍵值對O&#xff08;1&#xff09;&#xff0c;最壞情況O&#xff08;n&#xff09;&#xff0c;例如哈希沖突嚴重時。【n是一個哈希桶的元素數量】 unordered_map特性 鍵值對存儲&#xff…

C++ 頭文件說明

如果一個程序足夠大&#xff0c;代碼功能很多&#xff0c;可以想象&#xff0c;不可能把代碼寫在一個cpp文件里。我們需要模塊化&#xff0c;這樣的好處很多&#xff0c;方便分工合作&#xff0c;可讀性提高&#xff0c;調用也方便。 這個要怎么做呢&#xff1f; 很簡單直接當…

Lambda 表達式的語法:

在 Java 中&#xff0c;Lambda 表達式&#xff08;也稱為匿名方法&#xff09;是一種簡潔的表示方法接口&#xff08;Functional Interface&#xff09;實現的方式。它是 Java 8 引入的特性&#xff0c;目的是提高代碼的簡潔性和可讀性。 Lambda 表達式的語法&#xff1a; La…

C#零基礎入門篇(18. 文件操作指南)

## 一、文件操作基礎 在C#中&#xff0c;文件操作主要通過System.IO命名空間中的類來實現&#xff0c;例如File、FileStream、FileInfo等。 ## 二、常用文件操作方法 ### &#xff08;一&#xff09;文件讀取 1. **使用File.ReadAllText方法讀取文件內容為字符串** …

每日一題--內存池

內存池&#xff08;Memory Pool&#xff09;是一種高效的內存管理技術&#xff0c;通過預先分配并自主管理內存塊&#xff0c;減少頻繁申請/釋放內存的系統開銷&#xff0c;提升程序性能。它是高性能編程&#xff08;如游戲引擎、數據庫、網絡服務器&#xff09;中的核心優化手…

【Linux系統】Linux進程終止的N種方式

Linux系列 文章目錄 Linux系列前言一、進程終止的概念二、進程終止的場景三、進程終止的實現3.1 程序退出碼3.2 運行完畢結果正常3.3 運行完畢結果異常3.4 程序異常退出 總結 前言 進程終止是操作系統中&#xff0c;進程的一個重要階段&#xff0c;他標志著進程生命周期的結束…

正則表達式引擎深入探討

正則表達式引擎&#xff08;Regular Expression Engine&#xff09;是正則表達式得以“活起來”的核心。它是一個精密的軟件組件&#xff0c;負責接收正則表達式和輸入文本&#xff0c;解析模式并執行匹配或替換操作&#xff0c;最終輸出結果——可能是簡單的“是否匹配”&…

java面試題,什么是動態代理?、動態代理和靜態代理有什么區別?說一下反射機制?JDK Proxy 和 CGLib 有什么區別?動態代理的底層

什么是動態代理&#xff1f; 動態代理是在程序運行期&#xff0c;動態的創建目標對象的代理對象&#xff0c;并對目標對象中的方法進行功能性增強的一種技術。 在生成代理對象的過程中&#xff0c;目標對象不變&#xff0c;代理對象中的方法是目標對象方法的增強方法。可以理解…

【工具類】Java的 LocalDate 獲取本月第一天和最后一天

博主介紹&#xff1a;?全網粉絲22W&#xff0c;CSDN博客專家、Java領域優質創作者&#xff0c;掘金/華為云/阿里云/InfoQ等平臺優質作者、專注于Java技術領域? 技術范圍&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大數據、物…

嵌入式開發之STM32學習筆記day06

基于STM32F103C8T6的開發實踐——從入門到精通01 1. 引言 STM32系列微控制器是STMicroelectronics推出的一款高性能、低功耗的32位微控制器&#xff0c;廣泛應用于嵌入式系統中。STM32F103C8T6是其中非常受歡迎的一款&#xff0c;憑借其強大的性能、豐富的外設接口和低廉的價格…

學習使用 Git 和 GitHub 開發項目的教程推薦

Git 和 GitHub 是現代軟件開發中不可或缺的工具&#xff0c;無論你是個人開發者還是團隊成員&#xff0c;掌握它們都能極大提升效率。本文精選了一系列優質教程資源&#xff0c;涵蓋從基本 Git 命令到進階多人協作的內容。這些教程既有文字形式&#xff0c;也有視頻或交互式資源…

golang中的接口

1.簡介 在go中的接口是以一種類型,一種抽象的類型。接口(interface)是一組函數method的集合,go中的接口不能包含任何變量。在go中接口中的所有方法都沒有方法體,接口定義了一個對象的行為規范,只定義規范不實現。接口體現了程序的多態和高內聚低耦合的思想。go中的接口也是…

AI 浪潮下,職場的變與不變

如今&#xff0c;AI 如迅猛颶風&#xff0c;極速席卷職場&#xff0c;徹底攪亂了原有的秩序。你是否留意到&#xff0c;身邊的工作方式正悄然生變&#xff1f;今天&#xff0c;【探星 AI 研習社】就為大家深入剖析&#xff0c;AI 如何改寫職場劇本。無論你是大學生還是職場資深…