闫氏DP分析法
本节是学习了B站一个大佬up主大雪菜的DP分析方法,这个UP主的很多解题思路都总结的很好,之所以叫闫氏分析法是因为UP主姓闫,这是听课笔记。下面是课程链接:
https://www.bilibili.com/video/BV1X741127ZM?from=search&seid=1472099735408379551
核心:从集合角度分析DP问题
1.状态表示 f(i)
- 集合,划分依据:寻找最后一个不同点
- 属性 max,min,count
2.状态计算,化整为零的过程
例题1,01背包问题
有N件物品和一个容量式V的背包,每件物品只能使用一次
第i件物品的体积式vi,价值是wi
求将这些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大
DP
状态表示:f(i,j)
集合:只考虑前i个物品,且总体积不超过j的选择方案集合
属性:max f(N,V)
状态计算:
集合1,不选第i个物品的方案,f(i-1,j)
集合2,选第i个物品的方案,f(i-1,j-vi)+wi
将集合1和集合2取最大值就是所求答案 max(f(i-1,j),f(i-1,j-vi)+wi)
/*01背包问题的朴素写法,二维数组*/
#include<iostream>
using namespace std;
const int N=1010;
int n,m;
int V[N],W[N];int f[N][N];
int main(){
cin>>n>>m;
for(int i=0;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]; //集合1的子集
if(j>=V[i]) f[i][j]=max(f[i][j],f[i-1][j-V[i]]+W[i]);
}
}
cout<<f[n][m]<<endl;
return 0;
}
/*01背包问题优化,一维数组*/
#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=0;i<=n;i++) cin>>V[i]>>W[i];
for(int i=1;i<=n;i++{
for(int j=m;j>=V[i];j--){
f[j] = max(f[j],f[j-V[i]]+W[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
例题2.完全背包问题
01背包中每个物品只能用1次,完全背包的每个物品可以用无限次
把01背包问题从大到小遍历改为从小到大遍历就是完全背包
DP
状态表示:
集合:所有只从前i个物品中选,总体积不超过j的方案集合
属性:max
状态计算:
集合0:选0个第i个物品,f(i-1,j)
集合1:选1个第i个物品,f(i-1,j-Vi)+Wi
…
集合K:选k个第i个物品,f(i-1,j-kVi)+kWi
f(i,j) = max(f(i-1,j),f(i-1,j-vi)+Wi,…,f(i-1,j-kVi)+kWi)
f(i,j-vi) = max(f(i-1,j-Vi),f(i-1,j-2Vi)+Wi,…,f(i-1,j-kVi)+kWi)
立即推:f(i,j) = max(f(i-1,j),f(i,j-Vi)+Wi)
/*
01背包:f[i][j] = max(f[i-1][j],f[i-1,j-v]+w)
完全背包:f[i][j] = max(f[i-1][j],f[i][j-v]+w)
*/
#include<iostream>
using namespace std;
const int N=1010;
int n,m;
int V[N],w[N];
int f[N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;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;
return 0;
}
/*
完全背包问题优化
*/
#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<=n;i++){
for(int j=v[i];j<=m;j++){
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
例题三,合并石子问题
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量和,合并后与这两堆石子相邻的石子和新堆相邻,合并时由于选择的顺序不同,合并的代价也不同
比如有4堆 1 3 5 2 ,我们可以先合并1、2堆,代价为4,得到452,又合并1,2堆代价为9,得到9,2,再合并得到11,总代价为4+9+11
找出一种合适的方法,使总合并代价最小
DP
状态表示:
集合:所有将[i,j]合并成一堆的方案
属性:min
**状态计算:**f[i,j] = f[i,k]+f[k+1,j]+s[j]-s[i-1];
#include<iostream>
using namespace std;
const int N=310;
int n;
int s[N]; //前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++){
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;
return 0;
}
例题四:最长公共子序列
给定两个长度分别为N和M的字符串A,B,求即是A的子序列又是B的子序列的字符串长度是多少。
DP
状态表示:
集合:所有A[1,i]与B[1,j]的公共子序列的集合
属性:max
**状态计算:**f[i,j] = max(f[i-1,j],f[i,j-1],f[i-1,j-1]+1)
#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;
}