[每日算法】 背包问题

1.01背包

问题:

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。

求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

分析:

用dp[i][v]表示将前i个物品放入价值为v的背包能获得的最大价值

计算dp[i][v]时,分两种情况:第i件物品选或者不选,二者中价值较大的值就是f[i][v]的值,

即:dp[i][v]=max{dp[i-1][v],dp[i-1][v-c[i]]+w[i]}

注意: 矩阵c和w的第一位要空出来,因为i == 0的情况被跳过,之后的算法也是同理。

    vector<int> c = {0, 1, 2, 3}; // 费用
    vector<int> w = {0, 3, 2, 1}; // 价值
    int N = c.size()-1;
    int V = 2;
    vector<vector<int>> dp(N+1, vector<int>(V+1));
    for(int i = 0; i <= N; i++)
    for(int v = 0; v <= V; v++) {
        if(i == 0)
            dp[i][v] = 0;
        else if(v < c[i])              // 剩余空间不足选择第i件
            dp[i][v] = dp[i-1][v];
        else
            dp[i][v] = max(dp[i-1][v], dp[i-1][v-c[i]]+w[i]);
    }
    cout << dp[N][V] << endl;
考虑减少空间的开销,由于计算过程为从上到下,从左到右,dp[i][v]实际上只依赖于dp[i-1][0...v]这些值。如果使用从右到左的顺序,就可以只使用一维数组进行计算。

    vector<int> dp2(N+1);
    for(int i = 0; i <= N; i++)
    for(int v = V; v >= 0; v--) {
        if(i == 0)
            dp2[v] == 0;
        else if(v >= c[i])
            dp2[v] = max(dp2[v], dp2[v-c[i]]+w[i]);  // 一维矩阵实现
    }

2.完全背包

问题:

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。

求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大

分析:

用dp[i][v]表示将前i个物品放入价值为v的背包能获得的最大价值

计算dp[i][v]时,也是可以分为两种情况:第i件物品选n件(n>=1)或者不选,

也即: dp[i][v] = max(dp[i-1][v], dp[i][v-c[i]]+w[i])

需要注意,当选定第i件物品时,标号还是为i,因为选定之后还可以继续选i

    vector<vector<int>> dp(N+1, vector<int>(V+1));
    for(int i = 0; i <= N; i++)
    for(int v = 0; v <= V; v++) {
        if(i == 0)
            dp[i][v] = 0;
        else if(v < c[i])              // 剩余空间不足选择第i件
            dp[i][v] = dp[i-1][v];
        else
            dp[i][v] = max(dp[i-1][v], dp[i][v-c[i]]+w[i]);
    }

同样,完全背包也可以进行减少空间开销。相对于01背包,只需要修改v遍历的顺序就可以了。原因是:在01背包中,倒序的目的是保证决策是否选择i时依据的是没有选入i的子结果,而在完全背包中,我们正需要选入i的子结果,所以他们的顺序时相反的。

    vector<int> dp2(N+1);
    for(int i = 0; i <= N; i++)
    for(int v = 0; v <= V; v++) {
        if(i == 0)
            dp2[v] == 0;
        else if(v >= c[i])
            dp2[v] = max(dp2[v], dp2[v-c[i]]+w[i]);  // 一维矩阵实现
    }

3.多重背包

问题:

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

分析:

简单地,可以将第i个物品分为n[i]个物品来考虑,然后使用01背包的算法解决问题。但是这样会大量增加物品的数量,复杂度比较高。可以考虑将物品用二进制进行划分,分为:1, 2, 4, …, , n[i]-(-1),其中-1 < n[i] (-1也就是前面k项的和,它当然得比总的值要小)。因为1, 2, 4, …, 可以表示任意不大于的数,所以再加上n[i]-(-1),就可以表示所有不大于n[i]的数了。

int main() {
    vector<int> c = {1, 1, 1}; // 费用
    vector<int> w = {1, 2, 3}; // 价值
    vector<int> num = {5, 10, 15};
    int N = c.size();
    int V = 20;
    vector<int> C,W;
    C.push_back(0);
    W.push_back(0);  // 第一个值置空
    for(int i = 0; i < N; i++) {
        for(int k = 1; num[i] > 0;k*=2) {
            if(k > num[i])
                k = num[i];
            num[i] -= k;
            C.push_back(k*c[i]);
            W.push_back(k*w[i]);
        }
    }
    N = C.size()-1;
    vector<int> dp(V+1);
    for(int i = 0; i <= N; i++)
    for(int v = V; v >= 0; v--) {
        if(i == 0)
            dp[v] == 0;
        else if(v >= C[i])
            dp[v] = max(dp[v], dp[v-C[i]]+W[i]);  
    }
    cout << dp[V] << endl;    
}

其中中隐含了对指数的判断,简化了过程。之后再对C,W进行01背包的处理就行了。

4.混合背包

问题:

有的物品只可以取一次,有的物品可以取无限次,有的物品可以取的次数有一个上限。

分析:

这个问题实际上也就是前三个问题的叠加,首先还是使用二进制拆分的方式将只可以取有限次数的物品转变为只可以取一次的物品(多重背包问题),然后剩下的就是01背包和完全背包的混合,因为二者的实现只是在v的遍历顺序上有区别,只需要对物品i进行判断,对01背包进行逆序遍历,完全背包进行顺序遍历即可。

    vector<int> dp(V+1);
    for(int i = 0; i <= N; i++) {
        if(complete.find(i) != complete.end()) {
            for(int v = 0; v <= V; v++) {
                if(i == 0)
                    dp[v] == 0;
                else if(v >= C[i])
                    dp[v] = max(dp[v], dp[v-C[i]]+W[i]);  
            }
        } else {
            for(int v = V; v >= 0; v--) {
                if(i == 0)
                    dp[v] == 0;
                else if(v >= C[i])
                    dp[v] = max(dp[v], dp[v-C[i]]+W[i]);  
            }            
        }
    }

5.二维背包

问题:

二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。

分析:

多了一个条件,添加一维即可,同01背包。

    int dp[N+1][V1+1][V2+1] = {0};
    for(int i = 0; i <= N; i++)
    for(int v1 = 0; v1 <= V1; v1++) 
    for(int v2 = 0; v2 <= V2; v2++){
        if(i == 0)
            dp[i][v1][v2] = 0;
        else if(v1 < c1[i] || v2 < c2[i]) {
            dp[i][v1][v2] = dp[i-1][v1][v2];
        }
        else
            dp[i][v1][v2] = max(dp[i-1][v1][v2], dp[i-1][v1-c1[i]][v2-c2[i]]+w[i]);
    }
    cout << dp[N][V1][V2] << endl;

6.分组背包

问题:

有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

分析:

可以这样考虑,对于每个组,可以分为两种情况:选这一组中的物品,不选这一组中的物品

即: dp[k][v]=max{dp[k-1][v],dp[k-1][v-c[i]]+w[i]

    vector<vector<int>> c = {{},{1,1,1}}; // 费用
    vector<vector<int>> w = {{},{1,2,3}}; // 价值
    int N = c.size()-1;
    int V = 5;
    vector<vector<int>> dp1(N+1, vector<int>(V+1,0));
    for(int k = 0; k <= N; k++)
    for(int v = 0; v <= V; v++) {
        if(k == 0)
            dp1[k][v] == 0;
        else {
            for(int i = 0; i < c[k].size(); i++){
                if(v >= c[k][i])
                    dp1[k][v]=max(dp1[k-1][v],dp1[k-1][v-c[k][i]]+w[k][i]);
            }
        }
    }
    cout << dp1[N][V] << endl;
也可以使用一维数组减少空间的开销

    vector<int> dp(N+1, 0);
    for(int k = 0; k <= N; k++)
    for(int v = V; v >= 0; v--) 
    for(int i = 0; i < c[k].size(); i++){
        if(v >= c[k][i])
            dp[v]=max(dp[v],dp[v-c[k][i]]+w[k][i]);
    }
    cout << dp[V] << endl;

你可能感兴趣的:([每日算法】 背包问题)