闫氏dp分析法(学习笔记)

闫氏dp分析法

用于求有限集中的最值。方法:依次考虑,层层递进,将每一个条件从1开始考虑,再将每一种情况用数组的方式保存结果。那么当条件增加到2的时候就可以通过在条件为1的时候所保存的数据作为参考,得到条件为2时的最佳结果。
动态规划
状态表示 f< i >
集合
属性:max/min/count
状态计算

以整理视频中的几个问题来理解这个方法

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
之后的过程便可以类推了
动态规划
状态表示 f< i ,j >
集合
不选第i个物品,f< i-1 ,j >
选第i个物品,f< i-1 ,j-vi >+wi
属性:max f< V,N >
状态计算 maxf< i-1,j>,f<< i-1,j-vi>+wi>

代码如下:

//二维数组
#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背包一致。

动态规划
状态表示 f< i ,j >
集合
选0个第i个物品,f< i-1 ,j >
选1个第i个物品,f< i-1 ,j-vi >+wi
选2个第i个物品,f< i-1 ,j-2vi >+2wi
........
选2个第i个物品,f< i-1 ,j-kvi >+kwi
属性:max
状态计算 maxf< i-1,j>,f<< i-1,j-vi>+wi>

着重理解一下状态计算是怎么出来的

当考虑到第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
找出一种合适的方法,使总合并代价最小

动态规划
状态表示 f< i ,j >
集合:所有将< i,j>合并成一堆的方案
属性:min
状态计算 f< i,j>=minf< i,k>+f< k+1,j>+s< j>-s< i-1>

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的子序列的字符串长度是多少。

动态规划
状态表示 f< i ,j >
集合:所有将A<1,i>和B<1,j>的公共子序列的集合
属性:max
状态计算 f< i>< j>=maxf< i,j-1>,f< i-1,j>
如果a< i>==b< j>时,状态计算:f< i>< j>=f< i-1,j-1>+1

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.(略有删改)

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值