【算法学习笔记】27:区间DP

1 区间DP

区间DP指的是状态表示是某个区间的DP问题。

在区间DP里循环的顺序不太好写,因为要在计算时确保所需要的状态都计算过了,顺序一般可以先按区间长度从小到大来枚举。

2 模板题:石子合并

分析如下图。只要考虑最后一步,合并的是左右的哪两个区间,也就是枚举分界点 k k k,最后一步合并的就是区间 [ i , k ] [i, k] [i,k] [ k + 1 , r ] [k + 1, r] [k+1,r]
在这里插入图片描述

不管分界点 k k k怎么选,合并代价都是区间 [ i , j ] [i, j] [i,j]的区间和,这里用前缀和来维护,所以就是 s [ j ] − s [ i − 1 ] s[j] - s[i - 1] s[j]s[i1]

#include <iostream>

using namespace std;

const int N = 310;

int s[N];
int f[N][N];

int main() {
    // 读取n个石子并计算前缀和
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++ ) {
        cin >> s[i];
        s[i] += s[i - 1];
    }
    // 先遍历区间长度,由于长度是1时候合并代价是0,所以从2开始
    for (int len = 2; len <= n; len ++ ) {
        // 再遍历区间的左端点i
        for (int i = 1; i + len - 1 <= n; i ++ ) {
            // 则区间的右端点j可以唯一确定
            int j = i + len - 1;
            // 由于求min,所以初始化为INF
            f[i][j] = 1e8;
            // 遍历分界点k,将区间分成[i, k]和[k + 1, j]
            for (int k = i; k + 1 <= j; k ++ ) {
                // 左侧合并代价+右侧合并代价+本次代价(区间[i, j]和)
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
            }
        }
    }
    // 最后f[1][n]存的就是区间[1, n]的合并代价
    cout << f[1][n] << endl;
    return 0;
}

DP的边界是区间长度是 1 1 1的时候,由于不需要合并,合并代价就是 0 0 0。由于求 m i n min min的操作存在,每次计算 f [ i ] [ j ] f[i][j] f[i][j]之前都先置为 I N F INF INF。如果区间长度从 1 1 1开始遍历,就会把它赋值到 I N F INF INF,所以区间长度直接从长度 2 2 2开始,遍历到 n n n,区间长度是 1 1 1的情况直接用全局数组默认值 0 0 0就可以了。

3 例题:最长回文子序列

最长回文子序列也可以用区间DP写,更容易理解。

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

4 Java实现

模板题:石子合并

import java.util.*;


public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[] s = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            s[i] = scanner.nextInt();
            s[i] += s[i - 1];
        }

        int f[][] = new int[n + 1][n + 1];
        for (int len = 2; len <= n; len++) {
            for (int i = 1; i + len - 1 <= n; i++) {
                int j = i + len - 1;
                f[i][j] = (int) 1e8;
                for (int k = i; k <= j - 1; k++)
                    f[i][j] = Math.min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
            }
        }

        System.out.println(f[1][n]);
    }
}

例题:最长回文子序列

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] f = new int[n][n];
        for (int len = 1; len <= n; len ++ ) {
            for (int i = 0; i + len - 1 < n; i ++ ) {
                int j = i + len - 1;
                if (len == 1) f[i][j] = 1;
                else if (s.charAt(i) == s.charAt(j)) f[i][j] = f[i + 1][j - 1] + 2;
                else f[i][j] = Math.max(f[i + 1][j], f[i][j - 1]);
            }
        }
        return f[0][n - 1];
    }
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页