洛谷P1757 通天之分组背包超级巨细详解 && DP入门

本文详细介绍了动态规划的基本概念及应用,并通过经典的背包问题进行深入浅出的解析。此外,还探讨了带有分组限制的通天之分组背包问题,并提供了一段完整的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,了解DP

 解决多阶段决策最优化问题的方法
 把所给求解问题的过程恰当地分成若干个相互联系的阶段
这个过程有点像分治法,但是不同于分治的是,分治是将问题分为子问题再逐个求解,而动态规划是将问题考虑当前情况再依次判断相关有联系的阶段,有点像树的结构。
用最简单的背包问题举例:
题:A有一个最大承重量为m的书包,一共有n个物品,分别的重量和价值为a,b,请问A能拿的最大价值是多少?
例如:
    m=8
序号 1 2 3 4
重量 4 3 3 5
价值 7 1 8 2
初始值dp[1][8]
从1开始,可以选择拿或者不拿
如果拿则变成了dp[1][8-4]+7
如果不拿则是dp[1][8]+0
到2
1)第一步拿了
   如果拿dp[2][4-3]+7+1
   不拿 dp[2][4]+7
2)第一步没那
   如果拿dp[2][8-3]+1
   不拿dp[2][8]+0
到3
1)第一步拿了,第二步没拿····
·······
·······
这样我们就可以求出每一个情况的最终价值,从而比较得出最大价值。
也可以画成二叉树更加形象。

因此dp解决的也是一个前后关联具有链状结构的多阶段过程

看到这我们也能想到dp于递归有点像,其实dp与递归在一部分题中也是能够互相转化的。


二,题练

洛谷P1757 通天之分组背包
在这里插入图片描述
 这里区分于上面讲的背包问题在于有无分组标号,该题加入了标号,因此在写的时候要注意每一个组中只能取一个。那么我们就要考虑应该怎样才能在选取的时候使每一个包都能取到。
&emdp;首先在输入的时候把重量和价值分别存入w【】和v【】中,再计算各个标号里面有几个数组用cnt【】++实现,最后再将dp【i】【j】=k存入数据,其中i表示组数,j表示在该组中的序次,k即dp【】【】表示该数在输入时的位置,即抛开组数的次序。
在这里插入图片描述

我们采取案例来示范:

m=45,n=3
10 10 1
10 5 1
50 400 2
cnt[1]=2 cnt[2]=1
dp[1][1]=1 dp[1][2]=2 dp[2][1]=3

进入r循环

i=1 -> j=m -> k=1 -> px=dp[1][1]=1 -> j=m=45>=w[1]=10
-> f[45]=max(f[45-10]+10,f[45]=0)=f[35]+10

k++<=2 -> px=2 -> j=45 >=10 -> f[45]=max(f[45-10]+5,f[45]) 注意:这里的f[45]不再是0而是上面求出来的f[35]+10
所以这里的值应该取上面的f[45]

j–遍历重量,这里可以求f[10]~f[34]
j=m-1 -> k=1 -> px=1 -> j=44>=10 -> f[44]=max(f[44-10]+10,f[44]) f[44]=0,所以f[44]=f[34]+10=10
j=m-1 -> k=2 -> px=2 -> j=44>=10 -> f[44]=max(f[44-10]+5,f[44]) f[44]=f[34]+10,所以f[44]=f[34]+10=10

j=m-2 -> k=1 -> px=1 -> j=43>=10 -> f[43]=max(f[43-10]+10,f[43]) f[43]=0,所以f[43]=f[33]+10=10
·······
j=10 -> k=1 -> px=1 -> j=10>=10 -> f[10]=max(f[0]+10,f[10]) f[10]=0,所以f[10]=f[0]+10=10
j=10 -> k=2 -> px=2 -> j=10>10 -> f[10]=max(f[0]+5,f[10]) f[10]=f[0]+10,所以f[10]=f[0]+10 =10
f[0]表示当剩余重量为0时的价值,此时已经不能再装下其它东西,所以f[0]=0 f[10]=10

i++ -> j=m -> k=1 -> px=3 -> j=45>50 X结束,

所以,这里f[j] = max(f[j - w[px]] + v[px], f[j]);的含义是什么呢?
其实就是判断同一组中取的最大价值元素!
比如1组中的a,b,c,求最适元素,同级取大即可!

源代码:

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdint>
#include<algorithm>
#include<climits>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<set>

using namespace std;
#define ll long long
#define MAX 10e7+3
#define MIN 1e-9
#define r(i,j,k) for(int i=j;i<=k;i++)
#define dr(i,j,k) for(int i=j;i>=k;i--)
#define clear(a,i) memset(a,i,sizeof(a))

int dp[107][107];
int cnt[107];
int v[100000];
int w[101000];
int f[10000005];//表示最终价值

int main(void)
{
	int m, n;
	cin >> m >> n;
	int p, nmb = 0;
	r(i, 1, n)
	{
		cin >> w[i] >> v[i] >> p;
		nmb = max(nmb, p);
		cnt[p]++;
		dp[p][cnt[p]] = i;
	}
	r(i, 1, nmb)//i遍历组数
	{
		dr(j, m, 0)//j表示重量的剩余值
		{
			r(k, 1, cnt[i])//表示组数中的个数遍历
			{
				int px = dp[i][k];//px表示在大环境下的次序
				if (j >= w[px])//如果j》w该情况下表示w没有超出限制进入条件
				{
					f[j] = max(f[j - w[px]] + v[px], f[j]);
					//j-w[px]表示剩余的重量,
					//f[j-w[px]]表示剩余重量里面的价值,
					//f[]+v[]表示如果取值后的价值,
					//最后max(f[···],f[])取最大
				}
			}
		}
	}
	cout << f[m];
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值