動態規劃(詳解)

動態規劃一般可分爲線性動規,區域動規,樹形動規,揹包動規四類。

揹包問題:01揹包問題,完全揹包問題,分組揹包問題,二維揹包等

動態規劃的一般解題步驟:明確「狀態」 -> 定義 dp 數組/函數的含義 -> 明確「選擇」-> 明確 base case。

Leetcode322爲例
確定「狀態」,也就是原問題和子問題中變化的變量。由於硬幣數量無限,所以唯一的狀態就是目標金額 amount。

然後確定 dp 函數的定義:當前的目標金額是 n,至少需要 dp(n) 個硬幣湊出該金額。

然後確定「選擇」並擇優,也就是對於每個狀態,可以做出什麼選擇改變當前狀態。具體到這個問題,無論當的目標金額是多少,選擇就是從面額列表 coins 中選擇一個硬幣,然後目標金額就會減少:

揹包問題

0/1揹包:0-1揹包問題指的是每個物品只能使用一次

題目描述
給一個能承重V的揹包,和n件物品,我們用重量和價值的二元組來表示一個物品,第i件物品表示爲(Vi,Wi),問:在揹包不超重的情況下,得到物品的最大價值是多少?
在這裏插入圖片描述

輸入
第一行輸入兩個數 V,n,分別代表揹包的最大承重和物品數。
接下來nn行,每行兩個數Vi,Wi,分別代表第i件物品的重量和價值。
(Vi≤V≤10000,n≤100,Wi≤1000000)

輸出
輸出一個整數,代表在揹包不超重情況下所裝物品的最大價值。

樣例輸入1
15 4
4 10
3 7
12 12
9 8

樣例輸出1
19

數據規模與約定
時間限制:1 s
內存限制:64 M
100% 的數據保證(Vi≤V≤10000,n≤100,Wi≤1000000)

分析:
1.確定動歸狀態:ans[i][j]代表前i個物品在揹包最大承重j的最大價值
2.確定狀態轉移方程:

 if(w[i] > j){   //第i個物品的重量大於揹包最大承重j
                ans[i][j] = ans[i-1][j];
  }else{
                ans[i][j] = max(ans[i-1][j], v[i] + ans[i-1][j-w[i]]);

3.正確性證明:就不證明了
代碼演示

#include<iostream>using namespace std;int main(){
    int all, n,w[105],v[105],ans[105][10005];
    cin >> all >> n;
    for(int i = 1; i <= n; i++){
        cin >> w[i] >> v[i];
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= all; j++){
            if(w[i] > j){
                ans[i][j] = ans[i-1][j];
            }else{
                ans[i][j] = max(ans[i-1][j], v[i] + ans[i-1][j-w[i]]);
            }
        }
    }
    cout << ans[n][all]<<endl;
    return 0;
} 

數組壓縮

#include <iostream>
using namespace std;

int all, n, w[105], v[105], ans[10005];

int main() {
    cin >> all >> n;
    for (int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = all; j > 0; j--) { //從後向前
            if (j < w[i]) {
                break;
            } else {
                ans[j] = max(ans[j], v[i] + ans[j - w[i]]);
            }
        }
    }
    cout << ans[all] << endl;
    return 0;
}

完全揹包:完全揹包是指每種物品都有無限件可用

題目描述
有N種物品和一個容量爲 V 的揹包,每種物品都有無限件可用。
第 i 種物品的體積是Ci,價值是Wi。求解在不超過揹包容量的情況下,能夠獲得的最大價值。

輸入
第一行爲兩個整數N、V(1≤N,V≤10000),分別代表題目描述中的物品種類數量N和揹包容量V。
後跟N行,第 i 行兩個整數Ci、Vi,分別代表每種物品的體積和價值。

輸出
輸出一個整數,代表可獲得的最大價值。

樣例輸入
5 20
2 3
3 4
10 9
5 2
11 11

樣例輸出
30

數據規模與約定
時間限制:1s
內存限制:64M
對於100%的數據,1≤N,V≤10000。

#include <iostream>
using namespace std;

int all, n, w[10005], v[10005], ans[10005];

int main() {
    cin >> n >> all;
    for (int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= all; j++) {
            if (j >= w[i]) {
                ans[j] = max(ans[j], ans[j - w[i]] + v[i]);
            }
        }
    }
    cout << ans[all] << endl;
    return 0;
}

 

多重揹包:多重揹包是指每種物品都有幾件可用

題目描述
給有一個能承重 𝑉 的揹包,和n種物品,每種物品的數量有限多,我們用重量、價值和數量的三元組來表示一個物品,第 𝑖 件物品表示爲(𝑉𝑖,𝑊𝑖,𝑆𝑖),問在揹包不超重的情況下,得到物品的最大價值是多少?

輸入
第一行輸入兩個數𝑉、𝑛,分別代表揹包的最大承重和物品種類數。
接下來 𝑛 行,每行三個數 𝑉𝑖、𝑊𝑖、𝑆𝑖,分別代表第 𝑖 種物品的重量、價值和數量。

輸出
輸出一個整數,代表在揹包不超重情況下所裝物品的最大價值。

樣例輸入1
15 4
4 10 5
3 7 4
12 12 2
9 8 7

樣例輸出1
37

數據規模與約定
時間限制:1 s
內存限制:64 M
60% 的數據保證(𝑉𝑖≤𝑉≤1000,𝑛≤100,𝑆𝑖≤10,𝑊𝑖≤1000)
100% 的數據保證(𝑉𝑖≤𝑉≤100000,𝑛≤100,𝑆𝑖≤100000,𝑊𝑖≤1000)

#include <iostream>
using namespace std;

int all, n, ind, w[100005], v[100005], ans[100005];
int t[20];

int main() {
    int tt = 1;
    for (int i = 0; i < 20; i++) {
        t[i] = tt;
        tt *= 2;
    }
    cin >> all >> n;
    for (int i = 0; i < n; i++) {
        int x, y, z, temp = 0;
        cin >> x >> y >> z;
        while (z > 0) {
            ind++;
            if (z >= t[temp]) {
                w[ind] = x * t[temp];
                v[ind] = y * t[temp];
                z -= t[temp];
            } else {
                w[ind] = x * z;
                v[ind] = y * z;
                z = 0;
            }
            temp++;
        }
    }
    for (int i = 1; i <= ind; i++) {
        for (int j = all; j >= w[i]; j--) {
            ans[j] = max(ans[j], ans[j - w[i]] + v[i]);
        }
    }
    cout << ans[all] << endl;
    return 0;
}

線性動歸

線性DP最常見的有: 子集和問題,LIS問題,LCS問題。
子序列問題
子序列是不連續的序列,而子串兩者是連續的
子序列的解法有兩種模板
1、第一種思路模板是一個一維的 dp 數組:

int n = array.length;
int[] dp = new int[n];

for (int i = 1; i < n; i++) {
    for (int j = 0; j < i; j++) {
        dp[i] = 最值(dp[i], dp[j] + ...)
    }
}

舉個我們例子300. 最長上升子序列 (LIS),在這個思路中 dp 數組的定義是:
在子數組array[0…i]中,以array[i]結尾的目標子序列(最長遞增子序列)的長度是dp[i]。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int dp[nums.size()];
        for(int i = 0; i < nums.size(); i++){
            dp[i] = 1;
        }
        for(int i = 0; i < nums.size(); i++){
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]) dp[i] = max(dp[i],dp[j] + 1);
            }
        }
        int ans = 0;
        for(int i = 0; i < nums.size(); i++){
            if(ans < dp[i]){
                ans = dp[i];
            }
        }
        return ans;
    }
};

2、第二種思路模板是一個二維的 dp 數組:

int n = arr.length;
int[][] dp = new dp[n][n];

for (int i = 0; i < n; i++) {
    for (int j = 1; j < n; j++) {
        if (arr[i] == arr[j]) 
            dp[i][j] = dp[i][j] + ...
        else
            dp[i][j] = 最值(...)
    }
}

這種思路運用相對更多一些,尤其是涉及兩個字符串/數組的子序列。本思路中 dp 數組含義又分爲「只涉及一個字符串」和「涉及兩個字符串」兩種情況。

2.1 涉及兩個字符串/數組時(比如1143. 最長公共子序列(LCS)),dp 數組的含義如下:在子數組arr1[0…i]和子數組arr2[0…j]中,我們要求的子序列(最長公共子序列)長度爲dp[i][j]。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int dp[1005][1005];
        for(int i = 0; i < text1.size(); i++){
            dp[i][0] = 0;
        }
        for(int i = 0; i < text2.size(); i++){
            dp[0][i] = 0;
        }
        for(int i = 1; i <= text1.size(); i++){
            for(int j = 1; j <= text2.size(); j++){
                if(text1[i-1] == text2[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }else{
                    dp[i][j] = max(dp[i][j-1],dp[i-1][j]);
                }
            }
        }
        return dp[text1.size()][text2.size()];
    }
};

2.2 只涉及一個字符串/數組時,dp 數組的含義如下:

在子數組array[i…j]中,我們要求的子序列(最長迴文子序列)的長度爲dp[i][j]。

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int dp[1005][1005];
        for(int i = 0; i < s.size(); i++){
            dp[i][i] = 1;
        }
        for(int i = s.size()-1; i >= 0; i--){
            for(int j = i+1; j < s.size(); j++){
                if(s[i] == s[j]){
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                }else{
                    dp[i][j] = max(dp[i + 1][j],dp[i][j-1]);
                } 

            }
        }
        return dp[0][s.size()-1];
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章