闫氏DP分析法

闫氏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;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值