2184 cow exhibition

题意:给出N组数,每一组有Si和Fi两个数,从中选出若干组,使得Si的和TS与Fi的和TF相加最大,并且TS和TF都必须大于等于0。Si和Fi从-1000到1000。

分析:这又是一道典型的背包问题。我们这么想,设dp[i][j]是前i组数的TS为j时,TF的最大值。这样就把TS当成了体积,把TF当成了价值。只不过体积也是会不断变化的,不确定到底体积是多少,但一定是大于等于0的。因为Si从-1000到1000,所以TS的最小值就是-1000*100=-100000,因为背包问题需要参数都是正数,所以我们可以加一个100000的偏移。这样0对应-100000,100000对应0,200000对应100000。到最后求TS+TF时只需要求j-100000+dp[j]的最大值就可以了。这样就转化为01背包的思想。

但是需要注意的是,对于01背包来讲,应该用逆向循环来保证每个物品只被取一次,但是,对于负数的情况,如果从后向前循环,就会重复利用到当前物品已经被取的结果,变成完全背包,所以,负数的时候,应该从前往后顺序循环。即若转化为一维数组的01背包,为防止重复相加,当前Si<0时应该从左往右扫,Si>0时从右往左扫。再详细说一下:

在一般的01背包压缩空间的时候,体积的遍历是从大到小,因为dp[v]=max(dp[v],dp[v-c[i]]+w[i]),当前的dp[v]只取决于比自己小的dp[v-c[i]],所以从大到小遍历时每次dp[v-c[i]]和dp[v]都是上一次的状态。

如果体积为负v-c[i]>v,从大到小遍历dp[v-c[i]]是当前物品的状态,不是上一个,这样就会出错,解决的办法是从小到大遍历。

 

见代码:

#include <iostream>
using namespace std;
#define M 100000
#define MIN -999999
typedef struct _COW {
	int s;
	int f;
}COW;
COW cow[101];
int dp[2*M+1];

int max(int a, int b)
{
	return a>b?a:b;
}

int main(int argc, char **argv)
{
	int N;
	while (cin>>N) {
		for (int i=1; i<=N; ++i) {
			cin>>cow[i].s>>cow[i].f;
		}

		for (int i=0; i<=2*M; ++i)
			dp[i] = MIN;
		dp[M] = 0;//初始化,前0组数中取若干组,TS为0时,TF自然也是0.

		for (int i=1; i<=N; ++i) {
			if (cow[i].s<0 && cow[i].f<0)//剪枝
				continue;
			if (cow[i].s>0) {
				for (int j=2*M; j>=cow[i].s; --j)//0-1背包了,注意从大到小
					if(dp[j-cow[i].s]>MIN)//剪枝
						dp[j]=max(dp[j-cow[i].s]+cow[i].f,dp[j]);
			} else {
				for (int j=cow[i].s; j-cow[i].s<=2*M; ++j)//0-1背包,从小到大
					if(dp[j-cow[i].s]>MIN)//剪枝
						dp[j]=max(dp[j-cow[i].s]+cow[i].f,dp[j]);
			} 
		}

		int ans=MIN;
		for(int i=M;i<=2*M;i++)//剪掉了i<M的情况,即相对值i<0的情况;
		{
			if(dp[i]>=0)//这个一定要大于等于,因为fi可以为零 
				ans=max(ans,dp[i]+i-M);
		}
		cout<<ans<<endl;

	}
	
	system("pause");
	return 0;
}

172MS过!done!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值