POJ-2184:Cow Exhibition-01背包,正负数处理,小计

点击通往原题

最近几天一直写dp,每天都是Cows ,一会上山,一会挤奶,一会偷花,一会开车,今天又看到个智力为负的 orz

题意

给定 N N N组数据,每组数据包括 s i , f i s_i,f_i si,fi,数据有正有负,选择其中一些组,使得保证各组的 ∑ s , ∑ f \sum s,\sum f s,f之和分别大于0的前提下,所有数据之和 ∑ s + ∑ f \sum s+\sum f s+f最大。
*

这道题对初学者而言简直不要太残忍,不过用了半天时间AC之后感触颇深,写篇博客加深印象
主要难点

  1. 抽象,对初学者而言可能不太容易直接联想到0-1背包。
  2. 数据的正负问题,负数的处理至关重要。

好下来开始解题

定义全局变量

#define ll int
#define Max 105
#define inf 1000*100*2//100个cow的智慧都是1000,最大情况100*1000

为了处理负数带来的影响,数组要开比较大的size,怎么考虑呢。幸好数据范围不大 ,主要使用坐标轴整体右移的思路最多有100头牛,每个牛的智力范围在 -1000 – 1000 之间, 那他们的总和就是 落在 -100000 – 100000这个区间了里,也就是我们的dp要开200000这么大了。正数从100000开始计数,这样就将负数移到了0-100000的位置。

记得初始化,负数的初始化不能大意

	for (ll i = 0; i < 2 * inf; i++) dp[i] = -inf;
	dp[inf] = 0;

memset好像有bug,希望大佬能给出正确方式

重点来了,0-1背包的变形

  • s [ i ] > 0 s[i]>0 s[i]>0这就是正常背包,化简之后的形式就是从后到前进行更新, i f if if语句随后再讲。注意是 [ 0 , 2 ∗ i n f ] [0,2*inf] [02inf]区间都要进行背包,因为 d p [ ] dp[] dp[]必须考虑为负的情况,无论单个 s [ i ] s[i] s[i]是正是负。
  • s [ i ] < 0 s[i]<0 s[i]<0稍有特别,如果考虑质量 s [ i ] = − 4 s[i]=-4 s[i]=4,当前可用质量是 1 1 1,那么我们选了这个 c o w cow cow,这意味着啥呢,选他花费-4的代价,如果选了我们可用的质量是1-(-4)=5(总质量-代价),更新1位置,用到了5位置的元素,所以负数是从前往后更新的
	for (ll i = 0; i < n; i++) {
     //一层循环遍历所有奶牛
		//二重循环遍历所有质量,重点!!!
		//质量是正数就是正常的0-1背包,如果质量是负数,选这个物品的话dp数组要向右看,因此右侧
		//需要晚更新,从左到右更新
		/*
			dp[i]的含义,智力总和为i时,幽默感总数的最大值,i是智力总和,dp[i]是幽默感
		*/
		if (s[i] > 0) {
     
			for (ll j = 2 * inf - 1; j >= s[i]; j--) {
     //0-1背包从后往前进行迭代
				if (dp[j - s[i]] > -inf)//智力总和要能达到j-s[i]的程度
					dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
			}
		}
		else {
     
			for (ll j = 0; j < 2 * inf + s[i]; j++) {
     
				if (dp[j - s[i]] > -inf)
					//如果选了这个负数,那么对应的dp[j-s[i]]位置的element
					dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
			}
		}
	}

其实每个奶牛的核心问题只是在于选与不选,如果在 ∑ f = j \sum f=j f=j时选择i牛的话,那么减去选的这个奶牛的 s [ i ] s[i] s[i],剩余的 j − s [ i ] j-s[i] js[i]也必须是一个存在的,也就是可以通过已选的奶牛 ∑ f \sum f f计算出来的值,既然可以计算出来,那么肯定更新过,即条件 > − i n f >-inf >inf

好了,愉快的 A C AC AC,写的过程中发现POJ的数据不是一般的水,代码 [ ] [] []扩错了竟然都AC ORZ

最后附上完整代码,以防以后会用到

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

#define ll int
#define Max 105
#define inf 1000*100*2//100个cow的智慧都是1000,最大情况100*1000
#define p pair
ll n, s[Max], f[Max], ans = 0;
ll dp[2 * inf];

int main() {
     
	cin >> n;
	//以s[i]为质量,f[i]为价值,进行0-1背包的选择。
	for (ll i = 0; i < n; i++) {
      cin >> s[i] >> f[i]; }
	for (ll i = 0; i < 2 * inf; i++) dp[i] = -inf;
	dp[inf] = 0;
	for (ll i = 0; i < n; i++) {
     //一层循环遍历所有奶牛
		//二重循环遍历所有质量,重点!!!
		//质量是正数就是正常的0-1背包,如果质量是负数,选这个物品的话dp数组要向右看,因此右侧
		//需要晚更新,从左到右更新
		/*
			dp[j]的含义,智力总和为i时,幽默感总数的最大值,i是智力总和,dp[i]是幽默感
		*/
		if (s[i] > 0) {
     
			for (ll j = 2 * inf - 1; j >= s[i]; j--) {
     //0-1背包从后往前进行迭代
				if (dp[j - s[i]] > -inf)//智力总和要能达到j-s[i]的程度
					dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
			}
		}
		else {
     
			for (ll j = 0; j < 2 * inf + s[i]; j++) {
     
				if (dp[j - s[i]] > -inf)
					//如果选了这个负数,那么对应的dp[j-s[i]]位置的element
					dp[j] = max(dp[j], dp[j - s[i]] + f[i]);
			}
		}
	}
	for (ll i = inf; i < 2 * inf; i++) {
     
		if (dp[i] > 0)//此时f[i]的和是大于0的,是一个可以选择的状态
			ans = max(ans, dp[i] + i - inf);
		/*
		一开始不太理解为什么是i-inf,这里主要要搞清楚i是什么含义,dp[i]中的i表示的是质量的限制,
		也就是到这里用了多少质量了,可以看到dp没有从dp[i-1]->dp[i]的转化过程,也就是说,不等于
		-inf的项,都是可以达到的质量,
		*/
	}
	cout << ans << endl;
}

你可能感兴趣的:(算法)