HDU 5501 01背包 装满 变式*

01背包
二维数组存放

状态转移方程:(c[i][j]表示将前i个物品放入容量为j的背包中最能获得的最大价值)

if(j>=wi)

 c[i][j] = max{c[i-1][j], c[i-1][j-w[i]]+v[i]} 

else

 c[i][j]=c[i-1][j]

第0行初始化为0

i=1~N

   j=0~w

if(j>=wi)

c[i][j] = max{c[i-1][j], c[i-1][j-w[i]]+v[i]}

else

c[i][j]=c[i-1][j]

注意到第i行上的数据仅和第i-1行中的数据有关,即第i行上的每个数据是由第i-1行上的两个数据决定的,所以可以仅由一个一维数组来存放结果

初始化c的每个数据为0

i=1~N

  j=w~wi

    c[j]=max{c[j],c[j-wi]+vi}

注意点:

a.外层循环i代表前i个物品,比如i=1时,整个数组就覆盖了原来的数据,整个数组变成了原来的c[1][j],同理i=2,3,4也是如此

b.在进行每次循环的时候,要逆序进行覆盖,这是因为,01背包本质上还是用二维数组进行计算的,用二维数组计算的话,是一行一行计算的。也就是说,在前1到i-1行都填好的情况下,第i行根据第i-1行的数据进行计算。如果正序进行覆盖,则前面的值有可能发生改变,也就是第i-1行中的数据改变了,而后面的值,有可能用到这已经改变的前面的值,使结果不对。

比如c5可由原来的c5 c2(i-1行)获得,假如c5(i-1)的值改变,在接下来进行c7 c7有可能由c5 c7(i-1行)获得,那这样正序的话结果是不对的。

c.上述的j循环只从W降到wi,wi-1到0没有执行,这是因为不执行就相当于c[i]=ci,对应了二维数组中c[i][j]=c[i-1][j]的情况

网上还提到一个常数的优化,即在上述一维数组的情况下j循环到下限是max(W-sum(i-n)wi,wi),回到二维数组的情况,这是由于我们要求的c[n][w]=f(c[n-1][w],c[n-1][w-wn]),而c[n-1][w-wi]=f(c[n-2][w],c[n-2][w-wn-wn-1]),以此类推,c[1][w]=f(c[0][w],c[0][w-wn-wn-1-wn-2-~w1]),也就是说,对应循环i, 我只要循环下限到w-sum(i~n)wi就行了,所以优化过后的下限是ax(W-sum(i-n)wi,wi)

在这道题中,如果去掉Bi这个分量,那么就是01背包问题,但是有Bi这个分量就要考虑加入背包中的顺序了。不妨设X1X2……XN为“可以”装入背包的一种选择,
考虑其中一对XtXt+1,不管这两个数的前面的数和后面的数怎么变,XtXt+1和XtXt+1这两种摆法不一样的地方只有Bt+1Ct和BtCt+1,可以发现当前面的B/C大时,损失的分数较小。又由于这t是任意的,所以可以推广到全部的t(例如Xt+1Xt+2,不管前面的数和后面的数怎么变,只有保证Xt的B/C值比Xt+1的值大就可以了,推导过程也和上面类似),所以当以B/C的值从大到小排序时,用背包问题求得的解就是“这一种做法”的最大值。换句话说传统的01背包问题从许多种选法a1,a2,a3……at(ai对应可以装入背包的一种物品的选法)种获得最大值,而排序则保证了a1,a2,a3等分别获得最大值,即分别取得它们本身的最大,所以再用01背包问题获得最大值就是全部选法的最大值。

这个题目还有一个问题,即价值要减去装入背包的题目的时间和(代价和),01背包问题只求得了一个最大的值,并没有那么仔细的关注谁装入了背包,所以这又是一个问题。网上很多人说的设dp[i]为恰巧装满时的价值,但是转移方程还是和01背包一样,并不能保证每步都是装满的。而且每步所减去的已装入背包的时间和是dp方程中的j,考虑到有可能没装满,这个值有可能是偏大的。

实际上传统01背包问题还有一个性质,即它的二维数组的每一行都是非递减的
只要j处的值比j-1处的值大,那j处的背包就是装满的(j为1时只要不是0就可以了)就比如这样的
在这里插入图片描述

平的部分的点就是没有装满的。这些点都是属于能够装进去的,也就是dp[j-c[j]]
+a[j]这样子的,这样迭代到最后会出现dp[x]这个x很小没有东西能够装进去就是0了,所以都是不变的。而这题中减去的时间和是j,所以不会是平的,而是递减的
类似这样
在这里插入图片描述

在这里插入图片描述
每次递减的值就是1到i的物品中小于j的时间和,比如5为2+3,可以看到递减的还是个等差数列,所以要求最后到底最大值为多少,就是要求在“装满”的情况下的dp[i][j],由以上,要求的就是最后一行,每个递减的组的头部的那个数据就是装满的,即求最大值。因为后一个装满的不一定比前一个装满的大,比如第4行的48和69,同时最后一个也有可能处于递减中。综上,以下为代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int T;
int n, t;
typedef struct problem
{
	int a;
	int b;
	int c;
}problem;
problem pros[1001];
int dp[3001];
bool cmp(problem a, problem b)
{
	if (a.b*b.c> b.b*a.c)
	{
		return true;
	}
	return false;
}
int main()
{
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d%d", &n, &t);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d%d%d", &pros[i].a, &pros[i].b, &pros[i].c);
		}
		sort(pros + 1, pros + n+1, cmp);
		memset(dp, 0, sizeof(dp));
		for (int i = 1; i <= n; i++)
		{
			for (int j = t; j >= pros[i].c;j--)
			{
				dp[j] = max(dp[j], dp[j - pros[i].c] + pros[i].a-j*pros[i].b);
			}
			/*
			for (int k = 1; k <= t; k++)
			{
				printf("%d ", dp[k]);
			}
			printf("\n");
			*/
		}
		
		int maxx = -1;
		for (int i = 1; i <= t; i++)
		{
			maxx = max(maxx, dp[i]);
		}
		printf("%d\n", maxx);
		
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值