讲一下01背包,完全背包,多重背包,分组背包

01背包

[NOIP2005 普及组] 采药 - 洛谷

[USACO07DEC] Charm Bracelet S - 洛谷

现在有一个背包容量为t,有n个物品,每个物品有自己的重量wi,价值vi。

dp[i][j]表示前i个物品任意选择,在容量不超过j最大价值。

所以dp[n][t]表示答案。

显然dp[0][i]=0

对于每一个物品 有两种情况

1.不选:dp[i][j]=dp[i-1][j] :dp[i][j]表示前i个物品选不超过j的最大价值,i物品你又不选,是不是就相当于前i-1个物品任意选,因为没选,所以重量也不增加.

2.选:有个前提:背包能装下.例如现在容量不能超过j,你如果要装这个物品,必须有j>=w[i],

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

核心代码:

 //i:当前物品编号
 for (int i = 1; i <= m; i++)
 {
     //j:不超过j的容量
     for (int j = 0; j <= t; j++)
     {
         //i物品不要
         dp[i][j] = dp[i - 1][j];

         //i物品要
         //要i物品 就要承受cost[i]的容量 如果不超过j的容量
         if (j - cost[i] >= 0)
         {
             dp[i][j] = max(dp[i][j], dp[i-1][j - cost[i]] + value[i]);
         }
     }
 }

再来解释一下小问题

例如容量50

物品1:重量10 价值10

物品2:重量50 价值50

dp[1][50-10]=10没问题

dp[2][50-50]=10也没问题 前两个自由选择 容量不超 选择第一个

dp[2][50]=50 :dp[i][j] = max(dp[i][j], dp[i-1][j - cost[i]] + value[i]);

相当于我们选了物品2 加上对应的value 此时物品2不能再选 并且容量下降cost[i]

所以是dp[i-1][j-cost[i]]+value[i] 

这里是dp[2][50]=dp[1][0]+value[i]

依赖上面的格子和左上(可能有一段距离的格子)

你可能会有这么一个问题:i位置拿还是不拿,肯定拿啊,因为拿了价值就会增加,但是有容量作为限制条件,只能就是

1:不拿。2:拿,但是前面一部分就不能再拿了,因为拿就要给这个物品腾出w[i]的空间

当来到第i个物品,当空间限制在j时

2:拿:保证限制的空间大于等于物品容量:dp[i-1][j - cost[i]] + value[i] 这也就代表着把前面一些物品不拿了,拿的可能是1,2 i

1:不拿:可能拿的1 2 3 i-1

二者PK一下我们就能决定 是拿(1,2,i)好还是(1,2,3)好

空间压缩

dp数组的大小开的是容量m的大小

这个暂时我不理解 

	for (int i = 1; i <= n; i++)
	{
		for (int j = m; j >= w[i]; j--)
		{
			dp[j] = max(dp[j - w[i]] + d[i], dp[j]);
		}
	}
	cout << dp[m];

不过要说一下,为什么是从右向左

因为如果左->右

注意此时dp是一维,上图是想象中。(1,2)受到上面和左上影响,所以改掉他。但是右边一个如果要用蓝色箭头呢?

根据二维 dp 的状态转移方程,这个左边指的是第 i-1 行的左边,如果你从 左到右就先把 dp[j-cost[i]]给覆盖掉了,你再计算dp[j] 的时候读到的就是第i行的而不是第i-1行

完全背包

疯狂的采药 - 洛谷

也就是一个物品能取任意次

还是两种情况1:不拿 2:拿

如果不拿dp[i][j]=dp[i-1][j]

如果拿dp[i][j]=dp[i][j-cost[i]]

上图是01背包 拿的情况,完全体背包拿了这个物品,还能拿,所以...

然后完全体背包的状态转移方程是

dp[i][j]=max(dp[i-1][j],dp[i][j-cost[i]])

for (int i = 1; i <= m; i++) {
    for (int j = 0; j <= t; j++) {
        dp[i][j] = dp[i - 1][j];
        if (j - cost[i] >= 0) {
            dp[i][j] = max(dp[i][j], dp[i][j - cost[i]] + val[i]);
        }
    }
}

空间压缩

  for (int i = 1; i <= m; i++) {
      for (int j = cost[i]; j <= t; j++) {
          dp[j] = max(dp[j], dp[j - cost[i]] + val[i]);
      }
  }

多重背包

宝物筛选 - 洛谷

n个物品,容量为t,每个物品有weight,value,cnt

最普通尝试:
每个物品拿k个0<=k<=cnt[i]

 for (int i = 1; i <= n; i++) {
     for (int j = 0; j <= t; j++) {
         dp[i][j] = dp[i - 1][j];
         for (int k = 1; k <= cnt[i] && weight[i] * k <= j; k++) {
             dp[i][j] = max(dp[i][j], dp[i - 1][j - k * weight[i]] + k * value[i]);
         }
     }
 }

空间压缩

 for (int i = 1; i <= n; i++) {
        for (int j = t; j >= 0; j--) {
            for (int k = 1; k <= c[i] && weight[i] * k <= j; k++) {
                dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);
            }
        }
    }

当然会超时,现在用二进制分组优化

例如现在有一堆物品:重量=50W,价值=50V,数量=50;

根据2的次方分组,分成这样

现在有6个物品,每个物品有weight,value

一眼01背包,这是由于,对于i号物品,有cnt个,(0—cnt)范围内任意的数字都能用分组后来表示;

例如这个物品我想取10个,对应上图的(2+3),取11个,对应上图(0+2+3).

然后使用01背包就好啦

值得注意的是 题目给你n堆物品,dp要开大于n的长度

分组

 int m = 0;
 for (int i = 1; i <= n; i++)
 {
     int v, w, cnt;
     cin >> v >> w >> cnt;
     // 二进制分组
     for (int k = 1; k <= cnt; k <<= 1)
     {
         value[++m] = k * v;
         weight[m] = k * w;
         cnt -= k;
     }
     if (cnt > 0)
     {
         value[++m] = cnt * v;
         weight[m] = cnt * w;
     }
 }

状态压缩

for (int i = 1; i <= m; i++)
{
    for (int j = t; j >= weight[i]; j--)
    {
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}

完整代码

#include<iostream>
#include<vector>
#include<map>
#include<algorithm>
#include<cmath>
#include<string>
#define int long long
const int N = 4e4 + 10;
using namespace std;
int weight[N];
int value[N];
int dp[N];
int m = 0;
signed main() {

    int t, n;
    cin >> n >> t;
    int m = 0;
    for (int i = 1; i <= n; i++)
    {
        int v, w, cnt;
        cin >> v >> w >> cnt;
        // 整个文件最重要的逻辑 : 二进制分组
        for (int k = 1; k <= cnt; k <<= 1)
        {
            value[++m] = k * v;
            weight[m] = k * w;
            cnt -= k;
        }
        if (cnt > 0)
        {
            value[++m] = cnt * v;
            weight[m] = cnt * w;
        }
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = t; j >= weight[i]; j--)
        {
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }
    cout << dp[t] << endl;
 
    return 0;

}

分组背包

通天之分组背包 - 洛谷

有n个物品,还是重量和价值,不一样的是,某些物品是一组的,每个组你只能拿一个物品

dp[i][j]表示前面i里面选了

两种情况:

1:我不选这个组dp[i][j]=dp[i-1][j]

2:选这个组:选这个组的谁好呢

这题,重点是去描述每个组的物品

这段代码起到了输入的作用,还有知道了一共有多少组,每个组有几个物品,每个组每个物品的下标.

例如c[5]=4表示5组有4个物品,二维数组g[][]存储下标

//n:n个物品
//m:m容量
int n, m, x, t = 0;
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
	cin >> a[i] >> b[i] >> x;
	//t:有多少组 
	//这道题的x是不会跳的 只会是连续的
	t = max(t, x);
	//表示x组有多少物品
	c[x]++;
	//表示x组的c[x]物品的下标位置
	g[x][c[x]] = i;
}

而下标是这么得到的:

输入1 1 1,t=1,c[1]=1,g[1][c[1]]=1--->g[1][1]=1--->一号物品

输入2 2 2,t=2,c[2]=1,g[2][c[2]]=2--->g[2][2]=2--->二号物品

输入3 2 1,t=1,c[1]=2,g[1][c[1]]=3--->g[1][2]=3--->三号物品

紧接着,遍历每一组,注意这里顺序很重要不能改(我不知道为什么)

for (int i = 1; i <= t; i++) {
	for (int j = m; j >= 0; j--) {
		for (int k = 1; k <= c[i]; k++) {
			//a[g[i][k]]表示第i组第k个物品的重量
			if (j >= a[g[i][k]]) {
				//b[g[i][k]]表示第i组第k个物品的价值
				dp[j] = max(dp[j], dp[j - a[g[i][k]]] + b[g[i][k]]);
			}
		}
	}
}

完整版

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
#define int long long
using namespace std;
const int N = 1e4+ 10;
int a[N], b[N], c[N];
int dp[N];
int g[N][N];
signed main()
{
	//n:n个物品
	//m:m容量
	int n, m, x, t = 0;
	cin >> m >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i] >> b[i] >> x;
		//t:有多少组 
		//这道题的x是不会跳的 只会是连续的
		t = max(t, x);
		//表示x组有多少物品
		c[x]++;
		//表示x组的c[x]物品的下标位置
		g[x][c[x]] = i;
	}
	for (int i = 1; i <= t; i++) {
		for (int j = m; j >= 0; j--) {
			for (int k = 1; k <= c[i]; k++) {
				//a[g[i][k]]表示第i组第k个物品的重量
				if (j >= a[g[i][k]]) {
					//b[g[i][k]]表示第i组第k个物品的价值
					dp[j] = max(dp[j], dp[j - a[g[i][k]]] + b[g[i][k]]);
				}
			}
		}
	}
	cout << dp[m] << endl;

	return 0;
}

  • 27
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值