动态规划之背包问题(三)

299 阅读1分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

多重背包问题

n个物品(每个物品有限个),每个物品v体积,w价值,有m个背包,如何取价值最大化

模板

优化前
#include <iostream>
using namespace std;
const int N = 110;
int f[N];
int n, m;
int main(){
    cin >> n >> m;  
    for (int i = 1; i <= n; i ++ ){
        int v, w, c;
        cin >> v >> w >> c;
        
        for (int j = m; j >= 0; j -- ) {
            for (int k = 1; k * v <= j && k <= c; k ++ ){
                f[j] = max(f[j], f[j - k * v] + w * k);
            }
        }
    }
    cout << f[m] << endl;
    return 0;
}

优化后

本次优化是时间复杂度的优化(二进制拆分法)

思想:用0到2^k可以表示0到2^k-1的任何数,通过该思想将多重背包转化成01背包

#include <bits/stdc++.h>
using namespace std;
const int maxn=25000;
int N,V;
int w[maxn],v[maxn];
int dp[maxn];
int main() {
	cin>>N>>V;
	int cnt=0;
	for(int i=1; i<=N; i++) {//预处理,将所有的数据转化成二进制的数相加
		int wi,vi,s;
		cin>>wi>>vi>>s;
		int k=1;		
		while(k<=s) {
			cnt++;
			w[cnt]=wi*k;
			v[cnt]=vi*k;
			s-=k;
			k*=2;
		}
		if(s>0) {
			cnt++;
			w[cnt]=wi*s;
			v[cnt]=vi*s;
		}
	}
	N=cnt;
	for(int i=1; i<=N; i++) {//与01背包相同,因为将多重转化为二进制的01背包
		for(int j=V; j>=w[i]; j--)
			dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	}
 
	cout<<dp[V]<<endl;
	return 0;

除了二进制拆分法,还有单调队列优化法,但是太难,不进行描述。


分组背包问题

n个物品(每个物品有限个),有很多组,每个组只能取一个,每个物品v体积,w价值,有m个背包,如何取价值最大化

可将每个组看成一个01背包问题,此时就将分组背包转化为多个01背包问题

分析

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

该公式表示:F[ i , j ] 从前 i 个物品中选出了总体积为 j 的物品放入背包的最大价值和

​ F [ i - 1 , j ] 表示未选第 i 个物品的最大价值

​ max 1-k( F [ i , j - v ] + w )表示选第 i 组个物品k(选1,2,3……个的总和)1-k表示第k组

模板

求最大价值

#include <iostream>
using namespace std;
const int N = 110;
int v[N][N], w[N][N];
int f[N], s[N];
int n, m;
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ){
        cin >> s[i];
        for (int j = 1; 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 = 1; 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;
}

求方案数

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 11, M = 16;
int n, m;
int w[N][M];
int f[N][M];
int w0[N];
int main(){
    cin >> n >> m;

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

    cout << f[n][m] << endl;
    int j = m;
    for (int i = n; i; i -- )
        for (int k = 0; k <= j; k ++ )
            if (f[i][j] == f[i - 1][j - k] + w[i][k])
            {
                w0[i] = k;
                j -= k;
                break;
            }
    for (int i = 1; i <= n; i ++ ) cout << i << ' ' << w0[i] << endl;
    return 0;
}