动态规划-区间、计数类DP问题总结

简介: 动态规划-区间、计数类DP问题总结

什么是动态规划

动态规划通过额外的空间将已经搜索过的相似的结果(指某些具有相同性质解的集合)用一个数组存起来,所以DP中的状态转移看上去是某两三个值之间的推导,其实是某两三个集合之间的状态转移!

常见的区间DP问题

石子合并

典型题例:

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。

每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。

每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。

例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;

如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。

问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

示例 :

4
1 3 5 2
输出:
22

思路

状态表示:集合f[i,j]表示所有将[i,j]合并成一堆的方法集合;属性:min

状态计算:最后一次合并肯定是[i,j]分成两堆,第一堆可以是i, i+1, i+2, ...,j-1;

枚举所有[i,j]中左右两边的堆的方法i~k, k+1~j,最后加上总代价s[j]-s[i -1]

状态转移f[i,j] = min(f[i,j], f[i,k] + f[k+1,j] + s[j] - s[i])

代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n, m;
int s[N];  //前缀和
int f[N][N];
int main() {
    cin >> n;
    //处理输入和前缀和
    for (int i = 1; i <=n; i ++) {
        cin >> s[i];
        s[i] += s[i - 1];
    }
    for (int len = 2; len <= n; len ++)  //枚举区间长度,len=1时就是一堆
        for (int i = 1; i + len - 1 <= n; i ++) {
            int j  = i + len - 1;
            f[i][j] = 1e8;  //初始设为无穷大
            for (int k = i; k < j; k ++)
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
        }
    printf("%d\n", f[1][n]);
    return 0;
}

计数类DP问题

整数划分

典型题例:

一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。

我们将这样的一种表示称为正整数 n 的一种划分。

现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。

示例 :

输入:
共一行,包含一个整数 n。
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对 109+7 取模。
5
输出:
7

思路

等价于完全背包问题:有一个容量为n的背包,且有n个物品,对应的体积分别为1~n,恰好装满背包的方案数

状态表示:集合f[i,j]表示所有从1~i中选,总体积恰好是j的选法的数量的集合;属性:数量

状态计算:f[i,j] = f[i-1,j] + f[i,j-i] 与完全背包优化方式一样:f[j] = f[j] + f[j-i]

代码:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n;
int f[N];
int main() {
    cin >> n;
    f[0] = 1;
    for (int i = 1; i <=n; i ++)
        for (int j = i; j <= n; j ++)
            f[j] = (f[j] + f[j - i]) % mod;
    printf("%d\n", f[n]);

充电站

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

相关文章
|
算法
求最大连续子段和 的 dp算法
对于这样的问题,我们可以直接用暴力,一个双重循环,虽说可以,但也没有更高明的方法? 我们再分析这个问题,如果我们知道了某个数前面一段数的和,我们就该考虑把这个数加入到前一段,还是重新开始一段。这个地方很重要,如果前一段的和小于0,我们重新建一段,反之加到前一段。这样我们就可以把n个数分成几段了,且每一段都求出了他们的和,然后再循环一次求出最大的一个和,我们就得到想要的结果了,也可以在分段的时候直接求结果。
63 0
|
10月前
计数dp之整数划分
计数dp之整数划分
|
10月前
|
算法 测试技术 C#
【状态机dp 动态规划】100290. 使矩阵满足条件的最少操作次数
【状态机dp 动态规划】100290. 使矩阵满足条件的最少操作次数
|
10月前
|
算法 测试技术 C#
【状态机dp 状态压缩 分组】1994. 好子集的数目
【状态机dp 状态压缩 分组】1994. 好子集的数目
|
10月前
|
算法 测试技术 C#
【map】【动态规划】LeetCode2713:矩阵中严格递增的单元格数
【map】【动态规划】LeetCode2713:矩阵中严格递增的单元格数
|
算法 程序员
【牛客算法BM2】 链表内指定区间反转
你好,欢迎来到我的博客!作为一名程序员,我经常刷LeetCode题目来提升自己的编程能力。在我的博客里,我会分享一些我自己做过的题目和解题思路,希望能够帮助到大家。今天,我想和大家分享一道挑战性较高的题目,让我们一起来挑战一下吧!作者也是在学习的过程中刷到有意思的题目就会选择与大家分享,并且提供较优解,关于力扣 的文章全部放在博客,如果大家喜欢记得支持作者。🤓
|
人工智能
(闫氏dp分析法)(线性dp)(区间类dp)AcWing 895. 最长上升子序列
(闫氏dp分析法)(线性dp)(区间类dp)AcWing 895. 最长上升子序列
124 0
|
C++
(排列,选择类dp)(数论同余定理,同余运算)(以背包为母题)1214. 波动数列
(排列,选择类dp)(数论同余定理,同余运算)(以背包为母题)1214. 波动数列
114 0