基本思路介绍:
首先是在看问题时,知道这道题要用dp去做,但是怎么去实现呢?空想是很难去构造式子的,闫氏dp方法的核心就是用集合的方法去分析问题,从集合的角度入手去解决问题。
新手入门一般就是求有限集合中的最值,比如大家常见的求一堆符合要求的方案中的最优值,这里就要分为两个阶段:化零为整,化整为零。第一是找出符合要求的方案,二是找出这些方案中的最优值(但有些题目也可能要让你找最优解、第k解,不定)。
闫氏dp分析法流程图:
1)状态表示 (对应化零为整的过程):我们每次去处理问题的时候,不是一个元素一个元素去枚举,而是每次枚举一类东西,比如去枚举一个子集,就是将一些有相似点的元素化成一个子集,然后用某一个状态来表示。
状态表示分为集合和属性:
集合:所有满足xx条件的集合,这就是 dp 数组所表示的集合。
属性(一般是最大值、最小值、个数、存在性)。
2)状态计算(对应化整为零的过程):我们要了解每个状态如何去算出来。这些子集一般要满足两个划分原则:不重复、不遗漏。先搞清楚 dp [ i ] 所表示的状态是什么,然后将其划分为几个部分,然后每个部分分别去求。相当于将 dp [ i ] 划分为若干个子集。比方说我们dp [ i ] 求最大值,那么我们先去求一下每一个子集的最大值,再对它们取一个max 就行了,这就相当于把一个整体问题转化成某些子集的问题去处理。
ps:划分原则也不是绝对的,比如“不重复”,在一些情况下是允许重复的,前提是不影响结果,比如求最大值。
集合划分的依据(常用套路):寻找最后一个不同点。
dp问题有很多种不同的模型,我们应先把各种dp模型整理出来,比如选择问题(背包模型),序列问题(最长上升子序列),状态压缩,状态积,区间dp,树形dp,单调积的优化,斜率优化,每个问题都要去整理一遍,遇到题目才会有经验去做,这是dp与其他章节不一样的地方。
用闫氏dp法进行例题分析:
01背包问题
题目大意:
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 v [ i ] ,价值是 w [ i ]。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示能取到的最大价值。
数据范围:
0< N,V ≤1000
0< vi , wi ≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
用闫氏分析法
分析01背包问题:
1、分析:
有限集合求最值,物品数量是有限的,是 n 个,总共的容量也是有限的,最大容量是 v 。我们要选一些物品装进背包,保证总体积不超过 v ,每个物品要么选要么不选两种情况,所以总共选法的数量最多就是2 ^ n 。我们从 2 ^ n 种情况里面找到能使总价值最大的方案。
状态表示:用 dp [ i ] [ j ] 表示从前 i 个物体中选取且总体积不超过 j 的选法。
状态计算:状态划分为以下两种情况:
①不选第 i 个物品,此时 dp [ i ] [ j ] = dp [ i - 1 ] [ j ]
②选择第 i 个物品,那么就要选取 dp [ i - 1 ] [ j - v [ i ] ] 的情况再加上 w [ i ] ,此时 dp [ i ] [ j ] = dp [ i - 1 ] [ j - v [ [ i ] ] + w[ i ]
对这两种可能取一个最大值,总状态转移方程为:
dp [ i ] [ j ] = max ( dp [ i - 1 ] [ j ] , dp [ i - 1 ] [ j - v [ i ] ] + w [ i ] )
③注意:只有 j >= w [ i ] 时才有第二种情况,需加上判断。
2、用闫氏分析法做出流程图:
如图做一个 dp [ i , j ] 集合,假设是一个椭圆,dp [ i , j ] 子集划分:通过找最后一个不同点,即选最后一个物品的状态不同,选第 i 个物品和不选第 i 个物品是不一样的。以这一点来划分整个集合。相当于把 dp [ i , j ] 划分为两个子集,左边为所有不选择第 i 个物品的方案 : dp [ i , j ] = dp [ i - 1 , j ] 。右边是所有选择第 i 个物品的方案 :dp [ i - 1 , j - v[ i ] ] + w [ i ] ]两边取 max 则是 dp [ i , j ]的最大值 :
dp [ i , j ] = max( dp [ i - 1 ] [ j ] , dp [ i - 1 ] [ j - v [ i ] ] + w[ i ] )。
朴素代码如下(后面有优化代码):
#include <stdio.h>
#include <iostream>
#include <string.h>
#define max(a, b) (a) > (b) ? (a) : (b);
const int maxn = 1010;
using namespace std;
int dp[maxn][maxn];
int w[maxn], v[maxn];//存物品的价值、体积
int main()
{
memset(dp, 0 ,sizeof(dp));
int n, m;//n为物品数量的,m为背包质量
scanf("%d %d",&n, &m);
for(int i = 1; i <= n; i++)
scanf("%d %d",&v[i], &w[i]);
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= m; j++)
{
dp[i][j] = dp[i-1][j];//左半边子集
if(j >= v[i])//右半边子集不一定存在,需要进行判断
dp[i][j] = max(dp[i][j], dp[i-1][j-v[i]] + w[i]);
}
}
printf("%d\n",dp[n][m]);
return 0;
}
完全背包
由经验可得,一般第一维是考虑前i个物品,后几维则是各种限制条件。
完全背包与01背包的区别在于每种物品都有无限件,而01背包问题每个物品最多只能用一次。
例题:
题目大意:
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。第 i 种物品的体积是 vi,价值是 wi。
求解哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式:
第一行有两个整数,N,V用空格隔开,分别表示物品总数和背包容积。
接下来有N行,每行两个整数vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式:
输出一个整数,表示最大值。
数据范围:
0 <= N, V <= 1000
0 <= vi, wi <=1000
输入样例:
4 5
1 2
2 4
3 4
4 5
输出样例:
10
再用闫氏dp分析法分析一波:
状态计算:因为每种物品都有无限多个,所以不能像01背包那样将集合单纯分为两个子集(选或不选)。应将集合划分成若干个子集