(文末有模板)背包问题(超级硬核)

看到文章末尾有模板福利哦~

介绍:

01背包

有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

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

二维dp解法:

动态规划数组定义

f[i][j]表示只考虑前i件物品,当背包容量为j时的最大价值。

一、如果装不下当前物品
那么前n个物品的最佳组合和前n-1个物品的最佳组合是一样的。

二、如果装得下当前物品
假设1:
装当前物品,在给当前物品预留了相应空间的情况下,前n-1个物品的最佳组合加上当前的价值就是总价值

假设2:
不装当前物品,那么前n个物品的最佳组合和前n-1个物品的最佳组合是一样的。
选取假设1和假设2中的较大的价值,为当前的最佳组合的价值。

如果你看懂了以上的所有的流程,那现在下面这个式子就可以理解了。
01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }

for (int i = 1; i <= n; i++)
    for (int j = 0; 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]);
	}

一维dp解法:
动态规划数组定义:

f[j]表示背包容量为j时,能装入的最大价值。

状态转移方程:

对于每个物品i,考虑是否放入背包中。若不放入,则总价值不变;

若放入,则总价值为f[j-v[i]] + w[i](在当前容量下,放入物品i后的总价值)。

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

 特别注意,我们要从后往前遍历,否则如果从前往后,我们使用的值就不是上一个物品的值。

文末有代码模板。

完全背包

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

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

二维dp解法:

动态规划数组定义

使用二维数组f[i][j],其中f[i][j]表示考虑前i件物品,当背包容量为j时的最大价值。

 

for (int i = 1; i <= n; ++i)
	{
		for (int j = 0; 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背包二维dp类似,当前背包容量为 i 时所得最大价值,必不小于背包容量为 i - 1 时的最大价值,故先令此时背包容量的最大价值为 f[i - 1][j],再判断是否能放的下每个不同的物品,如果能放下,则比较后取较大的值。

一维dp解法:

与01背包一维dp类似,但是此处是顺序遍历。

为什么是顺序遍历呢?我们观察01背包和完全背包可以看到,01背包用的是上一层的,完全背包用的是当前层的,所以必须要从前往后遍历。

文末有模板。

多重背包

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。

f[i,j] = max(f[i - 1][j - v[i] * k] + w[i] * k); k = 0, 1, 2, 3 ..., s[i]

跟01背包一样,主要是把多重拆成一重,文末有拆法的模板。

分组背包

给定N组物品和一个容量为V的背包。每组物品包含若干个,但在同一组内,你最多只能选择一件物品。每件物品有其对应的体积和价值。目标是选择一些物品放入背包,使得背包内物品的总体积不超过背包的容量,同时背包内物品的总价值尽可能大。

当第i组物品选0个也就是一个都不选的时候,其实就与从前i-1组物品选,且总体积不大于j的最大价值等价;
而当从第i组选择第k个物品时,可以由在前i-1组物品里选,总体积不大于j减去第i组的第k个物品的体积的最大价值再加上第i组的第k个物品的价值得到。

故可以得到状态转移方程为:

其实就是多次01背包,反复比较比max值就可以了。

下面有模板。

模板:

01背包模板

for(int i=1;i<=t;i++)
    for(int j=m;j>=v[i];j--)
        dp[j]=max(dp[j-v[i]]+w[i],dp[j]);//直接套01背包的板子

完全背包模板

for(int i=1;i<=t;i++)
    for(int j=v[i];j<=m;j--)
        dp[j]=max(dp[j-v[i]]+w[i],dp[j]);

多重背包模板

可以拆成完全背包问题,两种拆法

1. 一个一个拆

while(n--)
{
   cin>>v>>w>>s;
   while(s--)
   {
     a[++t]=v;
     b[t]=w;
   }
}

2. 二进制优化拆

for(int i=1,a,b,s;i<=n;i++)
	{
		cin>>a>>b>>s;
		int k=1;
		while(k<=s)//将每个物品都按照二进制合成
		{
			v[++cnt]=k*a;
			w[cnt]=k*b;
			s-=k;
			k*=2;
		}
		if(s)
		{
			v[++cnt]=s*a;
			w[cnt]=s*b;
		}
	}

然后调用01背包模板。

分组背包模板

可以看成01背包问题,对于每个物品,询问k次选不选,取最大的那个。

for (int i = 1; i <= n; ++i) // 遍历每一组物品  
    for (int j = m; j >= 0; --j) // 遍历背包容量从m到0  
        for (int k = 0; k < s[i]; ++k) // 遍历第i组中的每个物品  
            if (v[i][k] <= j) // 如果当前物品可以放入背包中  
                // 更新背包的最大价值,考虑放入当前物品或不放入的情况  
                f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值