基础算法学习---背包问题


背包问题

01背包

每件物品最多只用一次

01背包

将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。

为什么可以这样变形呢?我们定义的状态f[i][j]可以求得任意合法的i与j最优解,但题目只需要求得最终状态f[n][m],因此我们只需要一维的空间来更新状态。

(1)状态f[j]定义:NN 件物品,背包容量j下的最优解。

(2)注意枚举背包容量j必须从m开始。

(3)为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。

(4)例如,一维状态第i轮对体积为 33 的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。

(5)简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i] 。

作者:深蓝
链接:https://www.acwing.com/solution/content/1374/
//无优化,二维

#include
#include

using namespace std;

const int N = 1010;

int n,m;
int v[N],w[N];		//体积和价值
int f[N][N];		//状态

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

    for(int i = 1;i <= n;i ++) cin >> v[i] >> w[i];
//状态计算
    for(int i = 1;i <= n;i ++){
        for(int j = 1;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]);
        }
    }

    cout << f[n][m];
}


//优化,一维

#include
#include

using namespace std;

const int N = 1010;

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

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

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

    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];
}

完全背包

每件物品有无限个

完全背包

二维向一维转化的时候因为用到的是第 i 层更新所以不用逆序避免 i - 1 被 "污染"

//朴素做法
#include
#include

using namespace std;

const int N = 1010;

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

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

    for(int i = 1;i <= n;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 ++)
                f[i][j] = max(f[i][j],f[i - 1][j - v[i] * k] + w[i] * k);

    cout << f[n][m];
}

//优化一重循环
#include
#include

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];

    for(int i = 1;i <= n;i ++)
        for(int j = 1;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]);		//和01背包区别是从i层更新
    	  //if(j >= v[i]) f[i][j] = max(f[i][j],f[i - 1][j - v[i]] + w[i]); //01背包的更新
        }

    cout << f[n][m];
}

//优化至一维
#include
#include

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];

    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]);	//从第i层更新

    cout << f[m];
}

多重背包

每件物品个数给出

多重背包

//朴素写法
#include
#include

using namespace std;

const int N = 110;

int v[N],w[N],s[N];
int n,m;
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 = 1;j <= m;j ++)
            for(int k = 0;k <= s[i] && k * v[i] <= j;k ++)	//与完全背包不同之处
                f[i][j] = max(f[i][j],f[i - 1][j - v[i] * k] + w[i] * k);
    
    cout << f[n][m];
}

//二进制优化
#include
#include

using namespace std;

const int N = 25000;

int f[N];
int v[N],w[N];
int n,m;
int cnt = 0;

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

    for(int i = 0;i < n;i ++){
        int a,b,c;
        cin >> a >> b >> c;

//二进制拆分,可以将k个数组合成c,优化时间复杂度为log c
        int k = 1;
        while(k <= c){
            cnt ++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            c -= k;
            k *= 2;
        }

        if(c > 0){
            cnt ++;
            v[cnt] = a * c;
            w[cnt] = b * c;
        }
    }

    for(int i = 1;i <= cnt;i ++)
        for(int j = m;j >= v[i];j --)
            f[j] = max(f[j],f[j - v[i]] + w[i]);

    cout << f[m];
}

分组背包

每一组最多选一个物品

分组背包

//朴素写法
#include
#include

using namespace std;

const int N = 110;

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

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 = 1;j <= m;j ++){
            f[i][j] = f[i - 1][j];
            for(int k = 1;k <= s[i];k ++)
                if(j >= v[i][k]) f[i][j] = max(f[i][j],f[i - 1][j - v[i][k]] + w[i][k]);
        }

    cout << f[n][m];
}

//一维优化
#include
#include

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 = 1;j <= s[i];j ++)
            cin >> v[i][j] >> w[i][j];
    }

    //更新用到的数据是第i - 1层,所以要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];
}

相关