动态规划(详解)

动态规划一般可分为线性动规,区域动规,树形动规,揹包动规四类。

揹包问题: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];
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章