闫氏dp分析法
用于求有限集中的最值。方法:依次考虑,层层递进,将每一个条件从1开始考虑,再将每一种情况用数组的方式保存结果。那么当条件增加到2的时候就可以通过在条件为1的时候所保存的数据作为参考,得到条件为2时的最佳结果。
以整理视频中的几个问题来理解这个方法
01背包
题干:有N件物品和一个容量式V的背包,每件物品只能使用一次
第i件物品的体积式vi,价值是wi
求将这些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大
i:当前所剩物品总数
j:当前所剩体积
f[i][j]:当前背包价值
n:物品总数
m:体积总数
如果不放入,则i-1,其他不变。如果放入,则i-1 ,j-vi,总价值+wi,用一个j[x][y]数组来记录状态,x记录所剩物品数,y来记录所剩空间,j[x][y]整体赋予的值为背包内物品总价值
分析:
我们依次考虑N个物品,首先只有物品1,其体积为1,同时j=1时,此时最优的情况就是将物品1放入。f<1,1>=f[1][1]=f[i-1,j-V1]+W1=W1
接下来,继续得到物品2,此时i=2,j=1,W1<W2,V1=V2=1,之前物品1已放入背包。故初始化f<2,1>=f<2-1,1>=W1。由于j>=V2,所以我们可以考虑将物品1取出将物品2放入,然后比较前后差距,因为f[i-1][j-V2]+W2=W2>f[1][1],所以选择f<2,1>=f[2][1]=W2
如果j<m,那么就接下来考虑j=2时的选择,略加思考发现应该物品1,2都选,所以f<2,2>=f[i-1][j-V2]+W2=f[1][1]+W2=W1+W2
之后的过程便可以类推了
代码如下:
//二维数组
#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++) 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;
}
//一维数组,从二维数组改成一维数组,信息的修改就成了对数组的重新定义
#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++) 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;
}
完全背包
完全背包的每个物品可以用无限次,其他条件与01背包一致。
着重理解一下状态计算是怎么出来的
当考虑到第i个物品,并且体积为j时,最佳方案是f[i][j]=max(f[i-1][j],f[i-1][j-Vi]+Wi,f[i-1][j-2Vi]+2Wi ... f[i-1][j-kVi]+kWi)
当考虑到第i个物品,并且体积为j-v时,最佳方案是f[i][j-V]=max(f[i-1][j-Vi],f[i-1][j-2Vi]+Wi,f[i-1][j-3Vi]+2Wi ... f[i-1][j-kVi]+(k-1)Wi)
可见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++) cin>>V[i]>>W[i];
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++) 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[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
找出一种合适的方法,使总合并代价最小
len:考虑堆数
i:现考虑的最左堆
j:现考虑的最优堆
k:首先处理的第k堆
s[x]:直到第x堆共有多少石子
f[i][j]:在第i堆和第j堆中的最小代价
按照上面的🌰,最开始令len=2,i=1,j=2,那么k只能为1,f[1][2]=f[1][1]+f[2][2]+s[2]-s[0]=4。之后i++,i=2,j=3,f[2][3]=8,同理,f[3][4]=7
接下来len=3时
1⃣️i=1,j=3,k=i=1,此时先合并1、2堆,代价为4,得到452,又合并1,2堆代价为9,f[1][3]=f[1][1]+f[2][3]+s[3]-s[0]=8+9=17。
2⃣️如果k++,k=2,f[1][3]=f[1][2]+f[3][3]+s[3]-s[0]=4+9=13<17,所以f[1][3]=13。
3⃣️i++,i=2,k=2,f[2][4]=f[2][2]+f[3][4]+s[4]-s[0]=7+12=19。
4⃣️k++,k=3,f[2][4]=f[2][3]+f[4][4]+s[4]-s[0]=8+12=20>19。所以f[2][4]=19。
接下来的类推
#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的子序列的字符串长度是多少。
n:A的长度
m:B的长度
i:A的第i项
j:B的第j项
f[i][j]:考虑到A的第i项和B的第j项时,最长子序列长度
通过样例分析,A=acbd,B=abedc
1⃣️i=1,j=1时,a[i]==b[j],j[1][1]=f[0][0]+1=1
2⃣️i=1,j=2时,a[i]!=b[j],又因为j[0][2]=0<j[1][1]=1,所以f[1][2]=1,同理f[1][3]=1,f[1][4]=1,f[1][5]=1
3⃣️f[2][1]=f[1][1]=1,f[2][2]=f[1][2]=1。
4⃣️i=2,j=3时,a[i]!=b[j],f[1][3]=1==f[2][2]=1,所以f[2][3]=1。同理f[2][4]=1,f[2][5]=f[1][5]+1=2
5⃣️f[3][1]=f[2][1]=1,f[3][2]=f[2][1]+1=2,f[3][3]=f[3][4]=f[3][5]=f[3][2]=2
后面类推
#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;
}
——————————————————————————————
本文参考教学视频:link.
代码整理取自:XinyueRao的文章link.(略有删改)