背包问题总结

背包问题总结

01背包问题

AcWing 2.01背包问题


AcWing 打卡
另外的参考

//01背包问题——每件物品最多只用一次 

/*
//二维动态规划

分析:

    f[i][j] 表示只看前i个物品,总体积是j的情况下,总价值最大是多少。

    result = max{f[n][0~V]}     //答案

    f[i][j]:
        1. 不选第 i 个物品,f[i][j] = f[i-1][j]
        2. 选第 i 个物品,f[i][j] = f[i-1][j-v[i]]+w[i]
    f[i][j] = max{1. 2. }

    f[0][0] = 0

    时间复杂度:O(n^2)


代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main()
{
    //输入
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i];
    
    //f[0][0]=0;    // f属于全局变量,不需要再次初始化
    
    //dp过程
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
        {
            f[i][j]=f[i-1][j];
            if(j>=v[i])
                f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    
    
    ////寻找答案 result = max{f[n][0~m]}
    //int res=0;
    //for(int i=0;i<=m;i++)
    //    res=max(res,f[n][i]);
    //
    ////输出
    //cout<<res<<endl;
    
    
    //经分析,f[n][m]值最大
    cout<<f[n][m]<<endl;
    
    //结束
    return 0;
}
*/



/*
//一维优化:
分析:
    f[i]表示总体积是j的情况下,总价值最大是多少。
注意:
    需要倒着来(参考:https://www.luogu.com.cn/problem/solution/P1048)

*/

#include <bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int f[N];
int v[N],w[N];
int main()
{
    //输入
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i];
    
    //f[0][0]=0;    // f属于全局变量,不需要再次初始化
    
    //dp过程
    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]);
    
    
    //经分析,f[m]值最大
    cout<<f[m]<<endl;
    
    //结束
    return 0;
}

完全背包问题

AcWing 3.完全背包问题


AcWing 打卡

// 完全背包——每件物品有无数个

/*
//朴素写法
分析:
动态规划:
    1. 状态表示——f[i,j]
        (1). 集合:所有只考虑前i个物品,且总体积不大于j的所有选法
        (2). 属性:Max
    2. 状态计算——集合的划分
        方法:按第i个物品选了多少个划分(0,1,2,3,...,k-1,k)
        (1). 选0个:f[i-1,j]
        (2). 选k个:
            曲线救国:
                %1· 去掉k个物品i
                %2· 求Max,f[i-1,j-k*v[i]]
                %3· 再加回来k个物品i
            f[i,j]=f[i-1,j-v[i]*k]+w[i]*k
代码:

//此算法最坏时间复杂度为:O(10^9)
//所以可能过不了(看评测机忙不忙hhh,偶尔能过)

#include <bits/stdc++.h>

using namespace std;

const int N=1010;

int n,m;
int v[N],w[N];
int f[N][N];

int main()
{
    scanf("%d%d",&n,&m); //cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&w[i]); //cin>>v[i]>>w[i];
    
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k*v[i]<=j;k++) //注意:k不能无限大
                f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k); //找最大
                
    cout<<f[n][m]<<endl;
    
    return 0;
}
*/

/*
//优化:
分析:
        f[i,j]=f[i-1,j-v[i]*k]+w[i]*k
    f[i,j]=Max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2v]+2w , f[i-1,j-3v]+3w , ... )
    f[i,j-v]=Max(          f[i-1,j-v] ,   f[i-1,j-2v)+w ,  f[i-1,j-3v]+2w , ...... )
    
    所以:
        f[i,j]=Max(f[i-1,j],f[i,j-v]+w)

代码:
【如下】

#include <bits/stdc++.h>

using namespace std;

const int N=1010;

int n,m;
int v[N],w[N];
int f[N][N];

int main()
{
    scanf("%d%d",&n,&m); //cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&w[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];
            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;
}
*/

/*
//一维实现(最优)
//过程类似与01背包
    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)
//  01~是i-1,完全~是i

代码:
【如下】
*/

#include <bits/stdc++.h>

using namespace std;

const int N=1010;

int n,m;
int v[N],w[N];
int f[N];

int main()
{
    scanf("%d%d",&n,&m); //cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d%d",&v[i],&w[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]); //不存在01背包问题,完全背包本来就是f[i][]
                
    cout<<f[m]<<endl;
    
    return 0;
}

注意:如果状态转移时,用的是上次循环的结果(即 f[i-1][j])去一维时应倒序,否则(即 f[i][j])去一维时应正序。

多重背包问题

朴素版本

AcWing 4. 多重背包问题 I


AcWing 打卡

//多重背包——每件物品有一个具体数量s[i]
/*
分析:
    动态规划:
        一、状态表示  f[i,j]
            1. 集合:所有只从前i个物品中选,并且总体积不超过j的选法
            2. 属性:Max
        二、状态计算
            方法:按第i个物品选多少个划分集合(0,1,2,3,...,s[i])
            //与完全背包相似
            状态转移方程:
                f[i][j] = max(f[i-1][j-v[i]*k]+w[i]*k); (k=0,1,2,3,...,s[i])
            
朴素版本代码(暴力写法)://最坏时间复杂度O(n*m*s[i])
【如下】
*/

#include <bits/stdc++.h>

using namespace std;

const int N=110;

int n,m;
int v[N],w[N],s[N];
int f[N][N];

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i]>>s[i];
    
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++) //k有两个条件
                f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k); //取最大值
    
    cout<<f[n][m]<<endl;
    
    return 0;
}

优化版本

AcWing 5. 多重背包问题 II


AcWing 打卡

参考

//多重背包优化版本(暴力版本将会TLE)
/*

分析:
        原方程:f[i][j] = max(f[i-1][j-v[i]*k]+w[i]*k); (k=0,1,2,3,...,s[i])
    不能以完全背包的方法进行优化(会多出一项)
    
    二进制优化方式:
        主要思想:
            1. 将一个物品的数量按二进制的方式分为(log s[i])份(数量、价值、体积一起分)
                最终将分为:1,2,4,8,...,2^k,c  ( 2^(k+1)>s[i] , c<2^k )
            2. 按01背包实现
        【具体见视频hh】
        
代码:
    【如下】
*/

#include <bits/stdc++.h>

using namespace std;

const int N=12010,M=2010; //物品数量需要上取整, N=1000*log(2000)=12000; M为体积(用于动态规划), M=2000

int n,m;
int v[N],w[N];
int f[M];

int main()
{
    cin>>n>>m;
    
    int cnt=0; //分组的组别
    for(int i=1;i<=n;i++)
    {
        int a,b,s; //第i个物品的体积、价值和数量
        cin>>a>>b>>s;
        int k=1; //组别里面的个数,从1开始分
        while(k<=s)
        {
            cnt++; //组别先增加
            v[cnt]=a*k; //整体体积
            w[cnt]=b*k; // 整体价值
            s-=k; // s要减小
            k*=2; // 组别里的个数增加(二进制2^k)
        }
        if(s>0) //剩余的一组 —— 剩下的s就是分析里的c
        {
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    
    n = cnt ; //枚举次数正式由个数变成组别数

    //01背包一维优化
    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;
    
}

分组背包问题

AcWing 9.分组背包问题


AcWing 打卡

/*
分组背包 —— 每组物品有若干个,同一组内的物品最多只能选一个。

动态规划:
    状态表示f[i,j]:
        集合:只从前i组物品中选,且总体积不大于j的所有选法
        属性:Max
    状态计算:
        f[i-1, j-v[i,k]] + w[i, k]


*/

// 本处直接给出一维优化代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n, m;
int v[N][N], w[N][N], s[N];
int f[N];

int main()
{
    cin >> n >> m;

    for (int i = 1; i <= n; i ++ )
    {
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++ )
            cin >> v[i][j] >> w[i][j];
    }

    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 0; j -- )
            for (int k = 0; k < s[i]; k ++ )
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

    cout << f[m] << endl;

    return 0;
}
posted @ 2023-07-23 22:09  Mingrui_Yang  阅读(10)  评论(0编辑  收藏  举报