背包问题


什么是背包问题

背包问题指这样一类问题,题意往往可以抽象成:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。


一、01背包问题

1.问题描述: 有n件物品且每件物品只有一个和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

2.解题思路: dp问题首先解决:状态表示,状态集合,状态属性,集合划分。

1.状态表示:f[i][j]表示前i个物品中,背包体积为j的最优解(最大价值)。
2.状态集合:只从前i个物品选,背包总体积不大于j下的最优解。
3.状态属性:最优解为背包总价值最大。
4.集合划分:这部为求状态转移方程的关键,第i个物品集合可分为两种情况:不选第i件物品,选第i件物品。

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(f[i - 1][j] , f[i - 1][j - v[i]] + w[i])

n // 物品数
m // 背包容量
for (int i=1; i<=n; i++)
{
    for (int j=1; j<=m; j++)
    {
        f[i][j] = f[i-1][j];
         // 当背包容量小于物品容量j < v[i]时,f[i-1][j]代表一个集合。
        if (j >= v[i]) f[i][j] = max(f[i-1][j],f[i-1][j-v[i]] + w[i]);
    }
}

3.优化: 将二维优化成一维f[j],表示容量为j的最优解。

二维 f[i][较大体积] = max(f[i - 1][j] , f[i - 1][较小体积] + w[i]),可以观察到状态转移方程应该由上一个状态去更新此时状态。

一维 f[较大体积] = max(f[j] , f[较小体积] + w[i]),没办法自定义访问,那我们就要保证上一个状态不被更新。

如果从小到大遍历j那么f[较小体积] 就会被先更新,而后更新f[较大体积]就不是上一个状态的 f[较小体积] ,而是此状态的 f[较小体积]

解决方法:从大到小遍历j,这样更新f[较大体积]的就是上一个状态的 f[较小体积]

每次遍历时 f[j] = f[上一状态的 j]f[j - v[i]] + w[i] 比较大小更新 f[j]

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

二、完全背包问题

1.问题描述: 有n件物品且每件物品有无限个和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

2.解题思路: 与01背包类似只不过这里的物品是无限个,那么我们就枚举无限个,前提不超过背包容量。

for (int i = 1; i <= n; i++)
{
    for (int j = 1; 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] + k * w[i]);
            			// k = 0时 f[i][j] = f[i-1][j]
        }
    }
}

3.优化: 我们列举一下更新次序的内部关系

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-2*v]+2*w , .....)
由上两式,可得出如下递推关系: 
                        f[i][j]=max(f[i - 1][j], f[i][j - v] + w); 

有了上面的关系,那么其实k循环可以不要了

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

二维降到一维

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]);
	    // f[i][j] = max(f[i-1][j],f[j - v[i]] + w[i]);
	    // f[j] 为上轮的f[j]
	    // f[j - v[i]]为这轮确定
	}
}

三、多重背包问题

1.问题描述: 有n件物品且每件物品有S个和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

2.解题思路: 每件物品有S个,用 s[i] 来存每件商品的数量,与完全背包类似枚举每件物品的数量。

//二维
for (int i = 1; i <= n; i++)
{
    for (int j = 1; j <= m; j++)
    {
        for (int k = 0; k * v[i] <= j && k <= s[i]; k++)
        {
            f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
        }
    }
}

//一维
for (int i = 1; i <= n; i++)
{
	for (int j = m; j > 0; j--)
	{
	    for (int k = 0; k * v[i] <= j && k <= s[i]; k++)
	    {
	        f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
	    }
	}
}

3.优化: 这里优化采用二进制,例如 1,2,4,8 ··· 总数为 n ,利用 1,2,4,8 ··· 可以确定 1 ~ n 之间的所有数。

证明:

例如: 1,2,4 总数为 7。
1(001),2(010),3(001 + 010 = 011),4(100),5(100 + 001 = 101),6(110),7(111)
总结:有 001,010,100 就可确定 001 ~ 111 之间的所用数。

但如果总数时 10,方案为 1,2,4,8 总数是 12 比 10 要大,这时我们就选 1,2,4,3 总数为 10
其中 1,2,4 可以确定 1 ~ 7 之间的所有数,那么 1,2,4,3 可以确定 4 (1 + 3) ~ 10 (7 + 3) 与 1 ~ 7 的并集 1~10。

由上述证明,我们就可以把第 i 件物品 S 个 打包成 1,2,4,8 ··· 和为 S,若不能恰好为 S 那么利用上述总数时 10 的样例打包,这样物品就可以选 1 ~ S 个,被打包的可以看成一个新的物品,之后就是01背包问题。

for (int i = 0; i < n; i++)
{
    int a, b, c; cin >> a >> b >> c;
    int k = 1;
    while (k <= c)
    {
        v[++cnt] = a * k; // 打包后的体积
        w[cnt] = b * k; // 打包后的价值
        c -= k; // 打包完剩下的件数
        k <<= 1; // k =  1,2,4,8 ··· 
    }

    if (c != 0) v[++cnt] = a * c, w[cnt] = b * c;
    // 不能恰好等于,把剩下的打包
}

n = cnt;

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

四、分组背包问题

1.问题描述: 有n组物品且每组物品有若干个,同一组内的物品最多只能选一个,一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

直接上代码

// 二维
for (int i = 1; i <= n; i++)
{
	cin >> s[i]; // 记录第i组的物品的个数
	for (int j = 1; j <= s[i]; j++)
	{
		cin >> v[i][j] >> w[i][j]; // 第i组第j个物品
	}
}

for (int i = 1; i <= n; i++) // 枚举组数
{
	for (int j = 1; j <= m; j++) // 背包容量
	{
		for (int k = 0; k <= s[i]; k++) // 枚举i组的物品
		{
			if (j >= v[i][k]) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
			// 这里的if不可以写在for里面
			// 如果写在for里面那么第k个使得 j < v[i][k] 的物品就会直接退出for
			// 后面的 k + n 可能 j >= v[i][k]
		}
	}
}

//一维
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 (j >= v[i][k]) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
		}
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值