前言:1.背包问题就是一种组合优化的NP完全问题

           2.01背包问题是其他背包问题的理解基础

一.01背包

1.模板题【模板】01背包_牛客题霸_牛客网

<1>状态表式:

(1)不要求装满背包:dp[i][j]表示:在前i个物品中挑选,总体积不超过j,所有选法中,所能挑选的最大价值

(2)要求装满背包:dp[i][j]表示:在前i个物品中挑选,总体积等于j,所有选法中,所能挑选的最大价值

<2>状态转移方程:

(1) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])

(2)当要求装满背包时,用-1代表这种选法不成立,此时若是挑选i号物品,不仅要求j>=v[i],还要要求dp[i-1][j-v[i]]!=-1

<3>初始化:多开一行和一列

(1)当有0个物品时,想要总体积不超过j,只要不选物品即可,此时最大价值为0,故初始值均设为0

(2)当有0个物品时,想要总体积等于0,只要不选物品即可,此时dp[0][0]=0;但当有0个物品时,想要总体积等于j,不存在这种可能,故dp[0][j]=-1(j>=1)

<4>填表顺序:从上往下,从左往右填

<5>返回值:dp[n][V]

<6>代码实现

#include <iostream>
#include<vector>
#include<cstring>
using namespace std;

int v[1001],w[1001];
int n,V;


int main() {
    //读入数据
    cin>>n>>V;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }

    vector<vector<int>> dp(n+1,vector<int>(V+1));

    //不要求装满背包
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=V;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<dp[n][V]<<endl;

    memset(dp,0,sizeof(dp));
    //要求装满背包
    for(int j=1;j<=V;j++) dp[0][j]=-1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=V;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i] && dp[i-1][j-v[i]]!=-1) dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;

    return 0;
}

<7>空间优化:利用滚动数组

填第i行时只需用到第i-1行的第j列和第j-v[i]列数据,如此便可利用一维数组完成统计,此时状态转移方程为dp[j]=max(dp[j],dp[j-v[i]]+w[i])(j>=v[i])

注:为了保证第i-1行的第j-v[i]列数据不被覆盖,填表时需要从右往左填

<8>优化后的代码

#include <iostream>
#include<vector>
#include<cstring>
using namespace std;

int v[1001],w[1001];
int n,V;


int main() {
    //读入数据
    cin>>n>>V;
    for(int i=1;i<=n;i++)
    {
        cin>>v[i]>>w[i];
    }

    vector<int> dp(V+1);

    //不要求装满背包
    for(int i=1;i<=n;i++)
    {
        for(int j=V;j>=v[i];j--)
        {
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[V]<<endl;

    memset(dp,0,sizeof(dp));
    //要求装满背包
    for(int j=1;j<=V;j++) dp[j]=-1;
    for(int i=1;i<=n;i++)
    {
        for(int j=V;j>=v[i];j--)
        {
            if(dp[j-v[i]]!=-1) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<(dp[V]==-1?0:dp[V])<<endl;
    return 0;
}

2.01背包的特点:对于一个只有选与不选两种情况,且一个物品至多选一次

二.完全背包

1.模板题【模板】完全背包_牛客题霸_牛客网

<1>状态表式:

(1)不要求装满背包:dp[i][j]表示:在前i个物品中挑选,总体积不超过j,所有选法中,所能挑选的最大价值

(2)要求装满背包:dp[i][j]表示:在前i个物品中挑选,总体积等于j,所有选法中,所能挑选的最大价值

<2>状态转移方程:

(1) dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w[i])

(2)当要求装满背包时,用-1代表这种选法不成立,此时若是挑选i号物品,不仅要求j>=v[i],还要要求dp[i-1][j-v[i]]!=-1

<3>初始化:多开一行和一列

(1)当有0个物品时,想要总体积不超过j,只要不选物品即可,此时最大价值为0,故初始值均设为0

(2)当有0个物品时,想要总体积等于0,只要不选物品即可,此时dp[0][0]=0;但当有0个物品时,想要总体积等于j,不存在这种可能,故dp[0][j]=-1(j>=1)

<4>填表顺序:从上往下,从左往右填

<5>返回值:dp[n][V];

<6>代码实现:

#include <iostream>
#include<vector>
#include<cstring>
using namespace std;

const int N=1001;
int n,V;
int v[N],w[N];

int main() 
{
    cin>>n>>V;
    for(int i=1;i<=n;i++)
       cin>>v[i]>>w[i];
    vector<vector<int>> dp(n+1,vector<int>(V+1);

    //第一问
    //dp[i][j]:从前i个物品里面选,总体积不超过j,所有选法中能挑选出来的最大价值
    for(int i=1;i<=n;i++)//物品个数
    {
        for(int j=0;j<=V;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i]) dp[i][j]=max(dp[i][j],w[i]+dp[i][j-v[i]]);
        }
    }
    cout<<dp[n][V]<<endl;

    //第二问
    //dp[i][j]:从前i个物品里面选,总体积正好为j,所有选法中能挑选出来的最大价值
    memset(dp,0,sizeof(dp));
    //dp[i][j]==-1:不存在该种情况
    for(int i=1;i<=V;i++) dp[0][i]=-1;//初始化
    for(int i=1;i<=n;i++)//物品个数
    {
        for(int j=0;j<=V;j++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i] && dp[j-v[i]]!=-1) 
               dp[i][j]=max(dp[i][j],w[i]+dp[i][j-v[i]]);
        }
    }
    cout<<(dp[n][V]==-1?0:dp[n][V])<<endl;

    return 0;
}

<7>空间优化:

填第i行时只需用到第i-1行的第j列和第i行的第j-v[i]列数据,如此便可利用一维数组完成统计,此时状态转移方程为dp[j]=max(dp[j],dp[j-v[i]]+w[i])(j>=v[i])

注:填表时需要从左往右填即可,不会出现覆盖

<8>优化后的代码

#include <iostream>
#include<vector>
#include<cstring>
using namespace std;

const int N=1001;
int n,V;
int v[N],w[N];
int dp[N];

int main() 
{
    cin>>n>>V;
    for(int i=1;i<=n;i++)
       cin>>v[i]>>w[i];

    //第一问
    //dp[i][j]:从前i个物品里面选,总体积不超过j,所有选法中能挑选出来的最大价值
    for(int i=1;i<=n;i++)//物品个数
    {
        for(int j=v[i];j<=V;j++)
        {
            dp[j]=max(dp[j],w[i]+dp[j-v[i]]);
        }
    }
    cout<<dp[V]<<endl;

    //第二问
    //dp[i][j]:从前i个物品里面选,总体积正好为j,所有选法中能挑选出来的最大价值
    memset(dp,0,sizeof(dp));
    //dp[i][j]==-1:不存在该种情况
    for(int i=1;i<=V;i++) dp[i]=-1;//初始化
    for(int i=1;i<=n;i++)//物品个数
    {
        for(int j=v[i];j<=V;j++)
        {
            if(dp[j-v[i]]!=-1) 
               dp[j]=max(dp[j],w[i]+dp[j-v[i]]);
        }
    }
    cout<<(dp[V]==-1?0:dp[V])<<endl;

    return 0;
}

2.完全背包的特点:对于某个物品可以选多个

三.二维费用的背包问题

本质:就是有两个限制条件的背包问题

例题:一和零474. 一和零 - 力扣(LeetCode)

本质:有两个限制条件的01背包问题

1.状态表示:dp[i][j][k]表示:从前i个字符串中挑选字符,字符0的个数不超过j,字符1的个数不超过k,所有选法中的最大长度

2.状态转移方程:具体分析过程见01背包问题

3.初始化:初始值设为0

5.返回值:dp[len][m][n]

6.代码实现:

int findMaxForm(vector<string>& strs, int m, int n) {
        int len=strs.size();
        //动态规划解决问题
        vector<vector<vector<int>>> dp(len+1,vector<vector<int>>(m+1,vector<int>(n+1));
        for(int i=1;i<=len;i++)
        {
            //统计每个字符串中0和1的个数
            int a=0,b=0;
            for(char ch:strs[i-1])
            {
                if(ch=='0') a++;
                else b++;
            }
            for(int j=0;j<=m;j++)
            {
                for(int k=0;k<=n;k++)
                {
                    dp[i][j][k]=dp[i-1][j][k];
                    if(j>=a && k>=b) dp[i][j][k]=max(dp[i][j][k],dp[i][j-a][k-b]+1);
                }
            }
        }
        return dp[len][m][n];
    }

7.空间优化:具体分析参考01背包问题

8.优化后的代码:

int findMaxForm(vector<string>& strs, int m, int n) {
        int len=strs.size();
        //动态规划解决问题
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        for(int i=1;i<=len;i++)
        {
            //统计每个字符串中0和1的个数
            int a=0,b=0;
            for(char ch:strs[i-1])
            {
                if(ch=='0') a++;
                else b++;
            }
            for(int j=m;j>=a;j--)
            {
                for(int k=n;k>=b;k--)
                {
                    dp[j][k]=max(dp[j][k],dp[j-a][k-b]+1);
                }
            }
        }
        return dp[m][n];
    }

更多推荐