闫氏DP分析法
- 核心:从集合角度来分析DP问题;
- 目的:求有限集中的最值
动态规划
状态表示 f(i)
- 化0为整,把一类集合变成一个整体然后用一个数来表示它;
- 集合
- 属性:f(i)与集合的关系 (max/min/bool)
状态计算
化整为0的过程:将f(i) 划分为几个子集进行计算,分别求每个子集,最后将子集合并起来;
几个例题
01背包问题
选择问题
状态表示:
f(i,j)
- 集合:所有只考虑前i个物品,并且总体积不超过j的选法的集合;
- 属性:max 集合当中每一个方案的最大价值
就是只考虑前n个值,不超过v的最大的值
状态计算:
对于f(v,i), 可以分为两类,一类是不选择第i个物品的方案,一类是选择第i个物品的方案;
那么我们可以得到不选择i的物品的方案就是f(v,i-1), 选择第i个物品的最大值是
那么最后的结果就是:
AC代码为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include<iostream>
using namespace std; const int N = 2010;
int n,m; int v[N],w[N]; int f[N][N];
int main(){ cin >> n >> m; for (int i=1;i<=n; i++) cin >> v[i] >> w[i]; for(int i = 1; i <= n; i++){ for(int j = 0; j <= m; j++){ f[i][j] = f[i-1][j]; if(j >= v[i]) f[i][j] = max(f[i][j],f[i-1][j-v[i]] + w[i]); } } cout << f[n][m]; }
|
完全背包问题
状态表示:
f(i,j)
- 集合:所有只考虑前i个物品,并且总体积不超过j的选法的集合;
- 属性:max 集合当中每一个方案的最大价值
就是只考虑前n个值,不超过v的最大的值
状态计算:
对于f(i,j), 完全背包问题需要划分为多个集合;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| /* 01背包问题:f(i,j) = max(f[i-1][j], f[i-1]f[j-v] + w) 完全背包问题:f(i,j) = max(f[i-1][j], f[i]f[j-v] + w) */
using namespace std; const int N= 2010; int n,m; int v[N],w[N]; int f[N][N]; int main(){ cin >> n >>m; for (int i=1;i<=n;i++) cin >> v[i] >> w[i]; for (int i=1;i<=n;i ++){ for(int j=0;j<=m;j++){ f[i][j] = f[i-1][j]; if (j >= v[i]) f[i][j] = max(f[i][j], f[i][j-v[i]]+w[i]); } } cout << f[n][m] << endl; }
|
当然你还可以进行优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <iostream> using namespace std; const int N = 1010;
int n,m; int v[N],w[N]; int f[N] int main(){ cin >> n >> m; for(int i=1; i<=m; i++) cin >> v[i] >> w[i]; for(int i=1; i<=n; i++){ for(int j=v[i];j<=m,j++){ f[j] = max(f[j],f[j-v[i]] + w); } } return 0; }
|
石子问题
题目: https://www.acwing.com/problem/content/description/284/
状态表示:
f(i,j)
- 集合:所有将i到j的区间合并成一堆的方案的集合;
- 属性:min 集合当中每一个方案的最小代价;
状态计算:
对于f(i,j) 需要分为多个集合;
最后我们需要计算的结果就是 f(1,j)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream>
using namespace std;
const int N = 310; int s[N]; int f[N][N]; int 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 ++){ 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]); } } } cout << f[1][n] << endl; }
|
最长公共子序列
题目:https://www.acwing.com/problem/content/899/
状态表示:
f(i,j)
- 集合:所有A[1-i]与B[1-j]的公共子序列的集合
- 属性:max
状态计算:
对于f(i,j) 需要分为4种情况;在求最大值/最小值的时候情况可以重复但是不可以遗漏;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <iostream>
using namespace std;
const int N = 1010;
int n,m; char a[N],b[N]; int f[N][N];
int main(){ cin >> n >> m >> a + 1 >> b + 1; for(int i = 1;i <= n;i++){ for(int j =1; j<=m;j ++ ){ f[i][j] = max(f[i-1][j],f[i][j-1]); if(a[i] == b[j]) f[i][j] = max(f[i][j],f[i-1][j-1]+1); } } cout << f[n][m] << endl; return 0; }
|