背包问题:01背包,完全背包,多重背包,混合背包,二维费用背包,分组背包问题

参考link:AcWing题库

1、01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。输出选了哪些物品。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

题解

一、dp矩阵,横轴代表背包的容量,从0开始到V。纵轴代表物品数量,0代表前0个物品,1代表前1个物品,以此类推。为了方便理解,加上第0个物品,体积为0,价值为0。
二、dp[i][j]代表,当前容量是j,当前物品编号为i。
三、如果放不下物品i,那么前i个物品的最佳组合和前i-1个物品的最佳组合是一样的。即 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j] dp[i][j]=dp[i1][j]
四、如果放得下物品i。则有两种情况,放物品i和不放物品i。如果选择不放物品i,那么和(三)中情况一样, t e m p 1 = d p [ i − 1 ] [ j ] temp1 = dp[i-1][j] temp1=dp[i1][j]。如果选择放物品i,那么,首先计算当前容量j-物品i占据的空间剩余的空间left_c,这时最大价值为物品j的价值+剩余空间left_c所能达到的最大价值。由于每个物品只有一个,则剩余空间left_c的最大价值等于前i-1个物品时,剩余空间left_c的最大价值。即 t e m p 2 = v [ j ] + d p [ i − 1 ] [ l e f t _ c ] temp2 = v[j]+dp[i-1][left\_c] temp2=v[j]+dp[i1][left_c]
五、比较不放物品i的价值temp1和放物品i的价值temp2。选择价值大的作为结果。
样例

w = [1,2,3,4]
v = [2,4,4,5]

物品编号 \ 背包容量012345
0000000
1022222
2024666
3024666
4024668

判断哪些物品被选取了:
一、从表的右下角开始回溯。
二、从上面的题解中可以看出,如果前i个物品的最佳组合的价值和前i-1个物品最佳组合的价值一样,说明第i个物品没有被选择。否则,第i个物品被选择。
三、第i个物品被选择了之后,当前容量减去该物品体积得到剩余体积c,接下来选择的物品只能是比剩余体积c小的物品。则接着从剩余体积c,的前i-1个物品的表右下角开始回溯。

def backpack01(w,v,c):  #0,1 背包问题
    w = [0]+w   #第0个物品的体积
    v = [0]+v   #第0个物品的价值
    n = len(w)
    dp =[[0]*(c+1) for _ in range(n)]
    for i in range(1,n):
        for j in range(1,c+1):
            temp_c= j    #当前容量
            temp_w = w[i]  #这个物体的体积
            temp_v = v[i]
            if temp_c < temp_w:  #当前容量不能放下这个物体
                dp[i][j]=dp[i-1][j]
            else: #当前容量能装下这个物体
                #如果不装,则当前最大容量和
                temp1 = dp[i-1][j]
                #如果装,这当前价值为该物品价值与减去该物体体积后最大价值
                left_c = temp_c-temp_w
                temp2 = temp_v + dp[i-1][left_c]
                dp[i][j]=max(temp1,temp2)
    print(dp)
    max_value = dp[-1][-1]   #最大价值
    print(max_value)
    ans = []
    for i in range(4,0,-1):  #注意,第0个物品其实是没有的,所以这里不用考虑第0个物品
        if dp[i][c] != dp[i-1][c]:
            ans.append(i)
            c = c - w[i-1]
    print(ans)  #包里选了哪些物品

N,V = map(int,input().split())
print(N,V)
w = []
v = []
for i in range(N):
    t_w,t_v = list(map(int,input().split()))
    w.append(t_w)
    v.append(t_v)
backpack01(w,v,V)

2、完全背包问题

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

题解

这题和上一题类似。不同的是,每个物品可以被选择无数次。则第(四)会略有不同。
如果能够放下放物品i,那么同样也会有两种情况,选择放物品i和不放物品i。选择不放物品i时,和前面一样, t e m p 1 = d p [ i − 1 ] [ j ] temp1 = dp[i-1][j] temp1=dp[i1][j]。如果选择放物品i,首先计算放置了1个物品i之后剩下的容量left_c,这时最大价值为物品j的价值+剩余空间left_c所能达到的最大价值。由于物品i可以有无数个,则剩余空间left_c的最大价值等于前i个物品时,剩余空间left_c的最大价值。即 t e m p 2 = v [ j ] + d p [ i ] [ l e f t _ c ] temp2 = v[j]+dp[i][left\_c] temp2=v[j]+dp[i][left_c]
样例

w = [1,2,3,4]
v = [2,4,4,5]

物品编号 \ 背包容量012345
0000000
10246810
20246810
30246810
40246810
def full_backpack(w,v,c):  # 完全背包问题
    w = [0] + w  # 第0个物品的体积
    v = [0] + v  # 第0个物品的价值
    n = len(w)
    dp =[[0]*(c+1) for _ in range(n)]
    # print(dp)
    for i in range(1,n):
        for j in range(1,c+1):
            temp_c= j    #当前容量
            temp_w = w[i]  #这个物体的体积
            temp_v = v[i]
            if temp_c < temp_w:  #当前容量不能放下这个物体
                dp[i][j]=dp[i-1][j]
            else: #当前容量能装下这个物体
                # 如果不装,则当前最大容量和
                temp1 = dp[i - 1][j]
                # 如果装,这当前价值为该物品价值与减去该物体体积后最大价值
                left_c = temp_c - temp_w
                temp2 = temp_v + dp[i][left_c]
                dp[i][j] = max(temp1, temp2)
    print(dp[-1][-1])

3、多重背包问题

有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

题解

把问题当做01背包问题来解。
样例:

w = [1,2,3,4]
v = [2,4,4,5]
s = [3,1,3,2]
相当于:w = [1,2,3,4,1,1,3,3,4]
v = [2,4,4,5,2,2,4,4,5]

物品编号 \ 背包容量012345
0000000
1022222
2024666
3024668
4024668
5024688
60246810
70246810
80246810
90246810
	n = len(w)
    for i in range(n):
        si = s[i]
        while si-1 >0:
            si -= 1
            w.append(w[i])
            v.append(v[i])   #暴力当做01背包问题来解
    w = [0] + w  # 第0个物品的体积
    v = [0] + v  # 第0个物品的价值
    n = len(w)
    print(w,v)    #相当于背包问题的输入了
    dp = [[0] * (c + 1) for _ in range(n)]
    for i in range(1,n):
        for j in range(1,c+1):
            temp_c= j    #当前容量
            temp_w = w[i]  #这个物体的体积
            temp_v = v[i]
            if temp_c < temp_w:  #当前容量不能放下这个物体
                dp[i][j]=dp[i-1][j]
            else: #当前容量能装下这个物体
                # 如果不装,则当前最大容量和
                temp1 = dp[i - 1][j]
                # 如果装,这当前价值为该物品价值与减去该物体体积后最大价值
                left_c = temp_c - temp_w
                temp2 = temp_v + dp[i - 1][left_c]
                dp[i][j] = max(temp1, temp2)
    print(dp[-1][-1])

4、多重背包问题II

题目描述同上,但是输入样例的s的范围从0到2000。所以不能直接用上面的方法改成01背包问题。这里用到二进制优化的方法。

题解

引入:数字7,最少能用几个数字表示
考虑到二进制, 2 0 = 1 , 2 1 = 2 , 2 2 = 4 2^0=1,2^1=2,2^2=4 20=1,21=2,22=4
三个数字组合就能组成7:0=0; 1=1; 2=2; 3=1+2; 4=4; 5=1+4; 6=2+4; 7=1+2+4
当不能被整加表示时,例如数字10,则只需要用10-1-2-4=3,则用1,2,4,3这几个数字表示即可。8=1+3+4; 9=1+4+4; 10=2+4+4

则有当一个物体的数量为si时,可以将物体合并当做一个物体。例如这个物体数量为5,价值为2,体积为3。则可以拆分合并为:1个物体(w,v)=(2,3) + 2个物体:(22,32)=(4,6) + 2个物体:(4,6)。最后变成了3个物体,每个物体只有一个。
然后这个问题就转成了01背包问题。注意吧最后所有的物体按照容量排序后再按01背包问题解答。
样例

w = [1,2,3,4]
v = [2,4,4,5]
s = [3,1,3,2]
则相当于是(wi,vi):[(1,2),(2,4),(2,4),(3,4),(6,8),(4,5),(4,5)]
按照物体的体积排序后为:[(1,2),(2,4),(2,4),(3,4),(4,5),(4,5),(6,8)]

物品编号 \ 背包容量012345
0000000
1022222
2024666
30246810
40246810
50246810
60246810
70246810
def multi_backpack2(w,v,s,c):
    n = len(w)
    new_wv = []
    for i in range(n):
        si = s[i]
        step = 1/2
        for j in range(1,si,int(step*2)):
            si -= j
            new_wv.append((w[i]*j,v[i]*j))
        if si != 0:
            new_wv.append((w[i] * si, v[i] * si))
    # print(new_wv)   #[(1, 2), (2, 4), (2, 4), (3, 4), (6, 8), (4, 5), (4, 5)]
    new_wv.sort()
    # print(new_wv)   #[(1, 2), (2, 4), (2, 4), (3, 4), (4, 5), (4, 5), (6, 8)]
    #转换成 01 背包问题
    new_wv = [(0,0)] + new_wv
    print(new_wv)
    n = len(new_wv)
    dp = [[0] * (c + 1) for _ in range(n)]
    # print(dp)
    for i in range(1, n):
        for j in range(1, c + 1):
            temp_c = j  # 当前容量
            temp_w = new_wv[i][0]  # 这个物体的体积
            temp_v = new_wv[i][1]
            if temp_c < temp_w:  # 当前容量不能放下这个物体
                dp[i][j] = dp[i - 1][j]
            else:  # 当前容量能装下这个物体
                # 如果不装,则当前最大容量和
                temp1 = dp[i - 1][j]
                # 如果装,这当前价值为该物品价值与减去该物体体积后最大价值
                left_c = temp_c - temp_w
                temp2 = temp_v + dp[i - 1][left_c]
                dp[i][j] = max(temp1, temp2)
    print(dp[-1][-1])

5、混合背包问题

有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。

题解

就等于把前面的3种情况合并。把多重背包问题拆解成二进制的01背包问题。所以就相当于是01背包问题和完全背包问题。不同情况用不同的状态转移方程即可。
样例

w = [1,2,3,4]
v = [2,4,4,5]
s = [-1,1,0,2]
则相当于是(si,wi,vi):[(-1, 1, 2), (1, 2, 4), (0, 3, 4), (1, 4, 5), (1, 4, 5)]

物品编号 \ 背包容量012345
0000000
1022222
2024666
3024668
4024668
5024668
def hunhe(w,v,s,c):
    n = len(w)
    new_wv = []
    for i in range(n):
        si = s[i]
        if si < 0:  #01背包
            new_wv.append((-1,w[i],v[i]))
        elif si == 0: # 完全背包
            new_wv.append((0,w[i],v[i]))
        else: #多重背包,拆解
            step = 1 / 2
            for j in range(1, si, int(step * 2)):
                si -= j
                new_wv.append((j,w[i] * j, v[i] * j))
            if si != 0:
                new_wv.append((si,w[i] * si, v[i] * si))
    new_wv = [(0, 0,0)] + new_wv
    print(new_wv)
    n = len(new_wv)
    dp = [[0] * (c + 1) for _ in range(n)]
    for i in range(1, n):
        for j in range(1, c + 1):
            temp_c = j  # 当前容量
            temp_w = new_wv[i][1]  # 这个物体的体积
            temp_v = new_wv[i][2]
            if temp_c < temp_w:  # 当前容量不能放下这个物体
                dp[i][j] = dp[i - 1][j]
            else:  # 当前容量能装下这个物体
                # 如果不装,则当前最大容量和
                temp1 = dp[i - 1][j]
                # 如果装,这当前价值为该物品价值与减去该物体体积后最大价值
                left_c = temp_c - temp_w
                if new_wv[i][0] == 0:   #完全背包问题
                    temp2 = temp_v + dp[i][left_c]
                else:   #01 背包问题
                    temp2 = temp_v + dp[i - 1][left_c]
                dp[i][j] = max(temp1, temp2)
    print(dp[-1][-1])

6、二维费用背包问题

有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。
每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。

题解

二维费用同一维费用的思想一致,约束条件从一个变成两个而已。约束条件不仅要看当前容量能不能装下当前物体,也要看当前最大承重能不能装下当前物体。
此时的dp矩阵应该是一个三维的矩阵,维度为(N+1)×(V+1)×(M+1)。代表前i个物体时,体积为vi,最大承重为mi时最大价值。由于前i个物体的情况只与前i-1个物体的情况有关,所以无需记录整个3维矩阵,每次只需保留第i-1个物体时的dp[i-1]矩阵即可。
一、dp[i]矩阵,横轴代表背包的容量,从0开始到V。纵轴代表背包最大承重,从0开始到M,i代表前i个物品,为了方便理解,加上第0个物品,体积为0,价值为0。每次都要前i个物体,然后遍历从0到V(vi),从0到M(mi)的最大价值。
二、记录前i-1时的dp矩阵,存放为temp。
三、如果放不下物品i,那么前i个物品的最佳组合和前i-1个物品的最佳组合是一样的。即 d p [ m i ] [ v i ] = t e m p [ m i ] [ v i ] dp[mi][vi]=temp[mi][vi] dp[mi][vi]=temp[mi][vi]
四、如果放得下物品i。则有两种情况,放物品i和不放物品i。如果选择不放物品i,那么和(三)中情况一样, t e m p _ n o t _ a d d = t e m p [ i m i ] [ v i ] temp\_not\_add=temp[imi][vi] temp_not_add=temp[imi][vi]。如果选择放物品i,那么,首先计算当前容量j-物品i占据的空间剩余的空间left_v,以及当前最大承重-物体i的重量的剩余最大承重left_m,这时最大价值为物品j的价值+剩余空间left_v,最大承重为left_m时所能达到的最大价值。由于每个物品只有一个,则剩余空间left_v,最大承重为left_m时的最大价值等于前i-1个物品时,剩余空间left_v,最大承重为left_m时的最大价值。即 t e m p _ a d d = w [ i ] + t e m p [ l e f t _ v ] [ l e f t _ m ] temp\_add = w[i]+temp[left\_v][left\_m] temp_add=w[i]+temp[left_v][left_m]
五、比较不放物品i的价值temp_not_add和放物品i的价值temp_add。选择价值大的作为结果。

样例
N=4,V=5,M=6
v = [1,2,3,4]
m = [2,4,4,5]
w = [3,4,5,6]

当前0个物体时,无论体积容量多大,最大价值都是0

最大承重 \ 背包容量012345
0000000
1000000
2000000
3000000
4000000
5000000
6000000

当前1个物体时:

最大承重 \ 背包容量012345
0000000
1000000
2033333
3033333
4033333
5033333
6033333

当前2个物体时:

最大承重 \ 背包容量012345
0000000
1000000
2033333
3033333
4034444
5034444
6034777

当前3个物体时:

最大承重 \ 背包容量012345
0000000
1000000
2033333
3033333
4034555
5034555
6034788

当前4个物体时:

最大承重 \ 背包容量012345
0000000
1000000
2033333
3033333
4034555
5034566
6034788
def backpack2(v,m,w,V,M):
    # v 代表体积
    # m 代表重量
    # w 代表价值
    n = len(v)
    dp = [[0]*(V+1) for _ in range(M+1)]
    v = [0] + v
    m = [0] + m
    w = [0] + w
    for i in range(1,n+1):
        temp = copy.deepcopy(dp)  #第i个物体的情况只与第i-1个物体情况有关,所以前面i-2个物体的dp不需要记录了
        for mi in range(1,M+1):
            for vi in range(1,V+1):
                #如果装不下第i个物体
                if not (v[i]<=vi and m[i] <= mi):
                    dp[mi][vi] = temp[mi][vi]
                #如果装得下第i个物体
                else:
                    #可以选择装
                    left_v = vi - v[i]
                    left_m = mi - m[i]
                    temp_add = w[i] + temp[left_m][left_v]
                    #或者选择不装
                    temp_not_add = temp[mi][vi]  #同上面装不下时的结果是一样的
                    dp[mi][vi] = max(temp_add,temp_not_add)
        print(dp)
        print('=======================')
    print(dp[-1][-1])

7、分组背包问题

有 N 组物品和一个容量是 V 的背包。
每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N 组数据:
每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。

题解

同01背包问题一样,因为组内只能选取一个物体,所以前i个物体的问题变成前i组的最大价值问题。前i组最大价值只与前i-1组最大值有关,所以同上一题类似,只需要记录前i-1组时的最大价值的dp数组。
所不同的是,当是第i组物体时,遍历组内所有的物体(总物体个数为m),选取当前容量j下,能够放下的最大的价值的物体。如果当前容量下,第i组内任意一个物体都放不下,则 d p [ j ] = d p _ o l d [ j ] dp[j]=dp\_old[j] dp[j]=dp_old[j]如果当前容量下,第i组内很多个物体都能够满足,则遍历该组内所有的物体,每一个能放下的物体都放置一次。
假设第k个物体满足,可以选择添加该物体,则该物体的价值为 g o o d [ i ] [ k ] [ 1 ] good[i][k][1] good[i][k][1]。当前物体的体积为 g o o d s [ i ] [ k ] [ 0 ] goods[i][k][0] goods[i][k][0],剩余体积为 l e f t _ v = j − g o o d s [ i ] [ k ] [ 0 ] left\_v = j-goods[i][k][0] left_v=jgoods[i][k][0],当前容量当前物体的价值为 t e m p _ a d d = g o o d [ i ] [ k ] [ 1 ] + d p _ o l d [ l e f t _ v ] temp\_add =good[i][k][1] + dp\_old[left\_v] temp_add=good[i][k][1]+dp_old[left_v]
也可以选择不添加该物体, t e m p _ n o t _ a d d = d p _ o l d [ j ] temp\_not\_add = dp\_old[j] temp_not_add=dp_old[j]
比较添加该物体,不添加该物体,或者添加组内其他物体时的价值,选取最大的价值。 d p [ j ] = m a x ( t e m p _ a d d , t e m p _ n o t _ a d d , d p [ j ] ) dp[j] = max(temp\_add,temp\_not\_add,dp[j]) dp[j]=max(temp_add,temp_not_add,dp[j])
如果组内第k个物体不能满足当前容量,则当前最大价值为组内放了前k-1个的最大价值前i组的最大价值比较,选择大的做为当前最大价值。 d p [ j ] = m a x ( d p [ j ] , d p _ o l d [ j ] ) dp[j] = max(dp[j],dp\_old[j]) dp[j]=max(dp[j],dp_old[j])

样例
N=3,V=5
2
1 2
2 4
1
3 4
1
4 5

背包容量012345
0000000
1024444
2024468
3024468
def group_backpack(N,V):
    goods = []  #存放所有的物体
    for ni in range(N):  #N组物体
        n = int(input())  #第ni组物体里面有n个物体
        temp = []
        for i in range(n):
            vi,wi = list(map(int,input().split()))
            temp.append((vi,wi))
        temp.sort()  #将组内的物体按照体积的大小排序
        goods.append(temp)
    dp = [0]*(V+1)  #前0组物体
    for i in range(N):  #前i组物体
        dp_old = copy.deepcopy(dp)
        dp = [0] * (V+1)
        for j in range(1,V+1):  #当前容量为j
            #第i组物体的数量
            m = len(goods[i])
            for k in range(m):  #遍历该组内所有的物体
                if goods[i][k][0] <= j:  #说明装得下该物体
                    #如果装
                    left_v = j - goods[i][k][0]  #装了该物体后剩余体积
                    temp_add = goods[i][k][1] + dp_old[left_v]
                    #如果不装
                    temp_not_add = dp_old[j]  #则价值等于前i-1组物体的体积
                    #比较,注意这里也要比较组内前k-1个物体时可能有放得下的情况,更新过dp[j]
                    dp[j] = max(temp_add,temp_not_add,dp[j])
                else:  #装不下该物体了
                    dp[j] = max(dp[j],dp_old[j])  #将组内前面k-1个物体时的最大价值与前i组的最大价值做比较
        print(dp)
    print(dp[-1])
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值