POJ2184 Cow Exhibition - 动态规划(dp) - 01背包问题

原题目:

Cow Exhibition

Time Limit: 1000MS     Memory Limit: 65536K
Total Submissions: 17102     Accepted: 6998

Description

“Fat and docile, big and dumb, they look so stupid, they aren’t much
fun…”

  • Cows with Guns by Dana Lyons

The cows want to prove to the public that they are both smart and fun. In order to do this, Bessie has organized an exhibition that will be put on by the cows. She has given each of the N (1 <= N <= 100) cows a thorough interview and determined two values for each cow: the smartness Si (-1000 <= Si <= 1000) of the cow and the funness Fi (-1000 <= Fi <= 1000) of the cow.

Bessie must choose which cows she wants to bring to her exhibition. She believes that the total smartness TS of the group is the sum of the Si’s and, likewise, the total funness TF of the group is the sum of the Fi’s. Bessie wants to maximize the sum of TS and TF, but she also wants both of these values to be non-negative (since she must also show that the cows are well-rounded; a negative TS or TF would ruin this). Help Bessie maximize the sum of TS and TF without letting either of these values become negative.

Input

  • Line 1: A single integer N, the number of cows

  • Lines 2…N+1: Two space-separated integers Si and Fi, respectively the smartness and funness for each cow.
    Output

  • Line 1: One integer: the optimal sum of TS and TF such that both TS and TF are non-negative. If no subset of the cows has non-negative TS and non- negative TF, print 0.

Sample Input

5
-5 7
8 -6
6 -3
2 1
-8 -5

Sample Output

8
Hint

OUTPUT DETAILS:

Bessie chooses cows 1, 3, and 4, giving values of TS = -5+6+2 = 3 and TF
= 7-3+1 = 5, so 3+5 = 8. Note that adding cow 2 would improve the value
of TS+TF to 10, but the new value of TF would be negative, so it is not
allowed.
 
 

题目大概意思:

有N头牛(1 ≤ N ≤ 100),每头牛有两种属性S和F(-1000 ≤ S, F ≤ 1000),让你选出一个子集 S u b S e t SubSet SubSet,使得 ∑ S u b S e t ( S i + T i ) \sum_{SubSet}(S_i+T_i) SubSet(Si+Ti) 最大,且 ∑ S u b S e t S i ≥ 0 \sum_{SubSet}S_i ≥ 0 SubSetSi0 , ∑ S u b S e t T i ≥ 0 \sum_{SubSet}T_i ≥ 0 SubSetTi0 .

 
 

分析:

想要使 ∑ S u b S e t ( S i + T i ) \sum_{SubSet}(S_i+T_i) SubSet(Si+Ti) 最大,只需对于每一个可能的 ∑ S u b S e t S i \sum_{SubSet}S_i SubSetSi ,都求出最大的 ∑ S u b S e t ( S i + T i ) \sum_{SubSet}(S_i+T_i) SubSet(Si+Ti) ,最后再遍历每一个 ∑ S u b S e t S i \sum_{SubSet}S_i SubSetSi ,记录下 ∑ S u b S e t ( S i + T i ) \sum_{SubSet}(S_i+T_i) SubSet(Si+Ti) 的最大值。
对于附加要求 ∑ S u b S e t S i ≥ 0 \sum_{SubSet}S_i ≥ 0 SubSetSi0 , ∑ S u b S e t T i ≥ 0 \sum_{SubSet}T_i ≥ 0 SubSetTi0 可以最后再考虑。
 
 
回想最普通的01背包问题,我们利用动态规划求出了每一个可能的重量下的最大价值,如果我们令 ∑ S u b S e t S i \sum_{SubSet}S_i SubSetSi 是重量, ∑ S u b S e t ( S i + T i ) \sum_{SubSet}(S_i+T_i) SubSet(Si+Ti) 是价值,那么这道问题就转变为了普通01背包问题。因此我们用解决普通的01背包问题的方法解决这个问题,即以重量( ∑ S u b S e t S i \sum_{SubSet}S_i SubSetSi)作为下标创建dp数组:
 
 
d p [ a ] [ b ] = 只 考 虑 前 a 头 牛 的 情 况 下 , 总 重 量 ( ∑ S u b S e t S i ) = b 的 情 况 下 的 最 大 价 值 ( ∑ S u b S e t ( S i + T i ) ) dp[a][b] = 只考虑前a头牛的情况下,总重量( \sum_{SubSet}S_i )=b的情况下的最大价值(\sum_{SubSet}(S_i+T_i)) dp[a][b]=a(SubSetSi)=b(SubSet(Si+Ti)).
 
 
递推式也和普通01背包问题一样:

d p [ a + 1 ] [ b + S i ] = m a x ( d p [ a + 1 ] [ b + S i ] , d p [ a ] [ b ] + ( S i + T i ) ) dp[a+1][b+S_i] = max(dp[a+1][b+S_i],dp[a][b]+(S_i+T_i)) dp[a+1][b+Si]=max(dp[a+1][b+Si],dp[a][b]+(Si+Ti))
 
 
 

时间复杂度方面, ∑ S u b S e t S i \sum_{SubSet}S_i SubSetSi 可能取到 [ − 100 × 1000 , 100 × 1000 ] [-100×1000, 100×1000] [100×1000,100×1000] ,也就是有 2 × 1 0 5 2×10^5 2×105种可能的取值,共有100头牛,每头牛都要更新一遍 d p dp dp 数组,因此总时间复杂度是 O ( N 2 ∗ m a x S ) ≈ 2 × 1 0 7 O(N^2*maxS)≈2×10^7 O(N2maxS)2×107 ,递推式简单常数小,不会有问题。
 
 

可是我们记得普通01背包问题是可以通过滚动 d p dp dp 数组优化空间复杂度的,在本题中当然也可以,只是在普通01背包问题中,所有物品的重量都是正数,那么我们只需要选好遍历 d p dp dp 数组的方向就可以保证一定是以之前的值更新现在的值,比如刚刚的递推式:

d p [ a + 1 ] [ b + S i ] = m a x ( d p [ a + 1 ] [ b + S i ] , d p [ a ] [ b ] + ( S i + T i ) ) dp[a+1][b+S_i] = max(dp[a+1][b+S_i],dp[a][b]+(S_i+T_i)) dp[a+1][b+Si]=max(dp[a+1][b+Si],dp[a][b]+(Si+Ti))

优化为滚动版本就是 :

d p [ b + S i ] = m a x ( d p [ b + S i ] , d p [ b ] + ( S i + T i ) ) dp[b+S_i] = max(dp[b+S_i],dp[b]+(S_i+T_i)) dp[b+Si]=max(dp[b+Si],dp[b]+(Si+Ti))

对于该式,我们只需要让 a a a 从最大值递减就可以保证总是用之前的状态更新当前的状态。而在本题中,由于物品的重量可能会是负数,如果所有物品还按照同样的方向遍历,就会导致出现用当前物品已经更新过的状态来更新某一状态的错误,比如当这个递推式中 S i &lt; 0 S_i&lt;0 Si<0 时,如果还按照 a a a 递减的顺序遍历,就会使用右边的 d p dp dp更新左边的 d p dp dp ,而当 a a a 向左走到已经更新过的位置时,会以这个已经被更新过的状态来更新更左边的位置,即相当于用 d p [ a + 1 ] [ b ] dp[a+1][b] dp[a+1][b] 来更新 d p [ a + 1 ] [ b + S i ] dp[a+1][b+S_i] dp[a+1][b+Si] ,而这是错误的,因为正确的做法是用 d p [ a ] [ b ] dp[a][b] dp[a][b] 来更新 d p [ a + 1 ] [ b + S i ] dp[a+1][b+S_i] dp[a+1][b+Si] ,因此,对于 S i &lt; 0 S_i&lt;0 Si<0 的物品,要采用与 S i ≥ 0 S_i≥0 Si0 的物品相反的顺序更新 d p dp dp 数组。

 
 
另外是一些细节问题:

由于 ∑ S u b S e t S i \sum_{SubSet}S_i SubSetSi 会是负数,因此 d p dp dp数组应该开为双向的,我这里的做法是声明一个大小为 2 × 1 0 5 2×10^5 2×105的(带下划线的)_ d p dp dp 数组,然后声明一个 i n t int int 型指针 d p dp dp ,让这个 d p dp dp 指针指向 _ d p dp dp 的中间,就可以以负数下标访问了。

d p [ 0 ] dp[0] dp[0] 记得要初始化成0,也就是一头牛没有的时候,只有这一种状态,而其他的初始化成负无穷。

最后遍历 d p dp dp 数组得到答案的时候,要考虑背包的限制条件,就像普通01背包问题中只考虑重量小于等于 l i m i t w limit_w limitw 的,这里只考虑那些 a ≥ 0 a≥0 a0 d p [ a ] ≥ a dp[a]≥a dp[a]a 的,因为如果 d p [ a ] &lt; a dp[a]&lt;a dp[a]<a 意味着当 ∑ S u b S e t S i = a \sum_{SubSet}S_i=a SubSetSi=a 时,最大的 ∑ S u b S e t ( S i + T i ) \sum_{SubSet}(S_i+T_i) SubSet(Si+Ti) 也小于 a a a ,也就意味着最大的 ∑ S u b S e t T i \sum_{SubSet}T_i SubSetTi 也小于0,是不满足题目要求的。
 
 
下面贴代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int INF = 1 << 30;
const int MAX_N = 108;
const int MAX_W = 1008;

int _dp[2 * MAX_N * MAX_W];
int* const dp = _dp + MAX_N * MAX_W;// dp[i][j] : 从前i个物品中选, 重量为j时的最大价值

int main()
{
	int N, S, T;
	scanf("%d", &N);

	fill(_dp, _dp + 2 * MAX_N * MAX_W, -INF);
	dp[0] = 0;

	int lb = -1000 * N, rb = 1000 * N;  // 确定可能的重量的最大范围

	for (int i = 0; i < N; ++i)
	{
		scanf("%d%d", &S, &T);
		if (S <= 0 && T <= 0) continue; // 如果这头牛的S和T都为负, 则一定不选这头牛

		T += S;                         // T += S后, T为这头牛的价值
		if (S > 0)
		{
			for (int j = rb; j >= lb; --j)
			{
				dp[j] = max(dp[j], dp[j - S] + T);
			}
		}
		else
		{
			for (int j = lb; j <= rb; ++j)
			{
				dp[j] = max(dp[j], dp[j - S] + T);
			}
		}
	}
	int ans = -INF;
	for (int j = 0; j <= rb; ++j)
	{
		if (dp[j] >= j)
		{
			ans = max(ans, dp[j]);
		}
	}
	printf("%d\n", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值