Complete the Projects (hard version)dp

题目描述

The only difference between easy and hard versions is that you should complete all the projects in easy version but this is not necessary in hard version.

Polycarp is a very famous freelancer. His current rating is r units.

Some very rich customers asked him to complete some projects for their companies. To complete the i-th project, Polycarp needs to have at least a_iai​ units of rating; after he completes this project, his rating will change by b_ibi​ (his rating will increase or decrease by b_ibi​) (b_ibi​ can be positive or negative). Polycarp's rating should not fall below zero because then people won't trust such a low rated freelancer.

Is it possible to complete all the projects? Formally, write a program to check if such an order of the projects exists, that Polycarp has enough rating before starting each project, and he has non-negative rating after completing each project.

In other words, you have to check that there exists such an order of projects in which Polycarp will complete them, so he has enough rating before starting each project, and has non-negative rating after completing each project.

大意:Polycarp的初始评价值为r,当他选中事件(ai,bi)时,如果他当前的评价值r大于ai,那么他就能执行该事件,并且执行完后评价值变为r+bi。要注意的是r始终要求大于等于0.我们要求的是他所能经历的最大事件数目。

输入

The first line of the input contains two integers nn and rr (1≤n≤100,1≤r≤30000) — the number of projects and the initial rating of Polycarp, respectively.

The next nn lines contain projects, one per line. The ii-th project is represented as a pair of integers ai​ and bi​ (1≤ai​≤30000, −300≤bi​≤300) — the rating required to complete the ii-th project and the rating change after the project completion.

首行是n(代表接下来有几组数据,1≤n≤100),r(初始评价值,1≤r≤30000)

接下来n行每行两个,代表ai(要求的评价值,1≤ai​≤30000),bi(完成后评价值的改变量,−300≤bi​≤300)

输出

Print one integer — the size of the maximum possible subset (possibly, empty) of projects Polycarp can choose.

所能经历的最大事件数。

分析

首先考虑两种最简单的情况,即b>=0和b<0这两种情况。

当b>=0时,我们可以运用贪心策略,类似于游戏大鱼吃小鱼的玩法,先“吃掉”对评价值要求低的“小鱼”,提高评价值,然后吃掉稍大一点的“鱼”,最后吃掉所有能吃的鱼,大致思路如下。

for(int i=1;i<=k1;i++)
	{
		if(r>=h[i].a)
		{
			r+=h[i].b;
			ans++;
		}
		else
		{
			break;
		}
    }

tips:在吃鱼之前首先对事件进行排序,决定吃鱼的顺序,即根据ai对事件进行从小到大的排序。

当b<0时,情况就变得有些复杂,不像b>=0时只要简单的逻辑就能想明白。经历一个事件后,评价值r会减少,而这时想要满足r>=ai的条件就会变得有些困难。因此,我们不妨考虑一种更简单的情形,即ai为0的时候,这样我们只需要设法保证评价值r始终大于等于0即可。如果我们想的再深入些并且对dp有一定认识的话,就不难发现这是0/1背包问题。找到这个本质后,我们就可以尝试写出dp方程。其中dp[i][j]代表已经对前i-1件事件做出选择后,评价值(容积)为j的时候所经历的最大的事件数。

if(j+b[i]>=0&&j>=a[i]) b[i+1][j+b[i]]=max(b[i+1][j+b[i]],b[i][j]+1)

该方程表示是否选择第i件物品对结果的影响

b[i+1][j]=max(b[i+1][j],b[i][j]);

该方程表示推到下一个事件

搞定了主体部分后,再来考虑一些细节。比如这个dp的边界在哪里,比如事件的顺序是否对结果有影响。

关于边界,从实际意义出发,只有dp【1】【r】是有值的(如果从0开始就是dp【0】【r】)这个值就是之前b>=0的部分所能接受的最大事件数目,r是经历了这些事件后的评价值。

关于顺序,事实上,我认为这也是很容易被忽略或者搞错的地方。和普通的背包问题不同,它多了一个限定条件来让转移变得困难,而这个限制又和背包本身的容积相关,因此我们必须考虑顺序。这就是cmp2的由来。如果做过easy版本的大概很快就能明白cmp2的意义。事实上它也并不复杂,如果在b小于零的情况下尽可能多的经历事件,一个简单的想法是让每次经历事件后的评价值尽可能高,于是就有ai+bi,表示经历该事件后最低的评价值。对其排序即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,k,t,r;
struct node
{
	int a,b;
}h[110],l[110];
bool cmp1(node x,node y)//h
{
	if(x.a==y.a)return x.b>y.b;
	return x.a<y.a;
}
bool cmp2(node x,node y)//l
{
	return x.b+x.a>y.b+y.a;
}
int dp[110][60010];
int k1=0,k2=0,ans=0;
int main()
{
	scanf("%d%d",&n,&r);
	for(int i=1;i<=n;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		if(b>=0)
		{
			k1++;
			h[k1].a=a;
			h[k1].b=b;
		}
		else
		{
			k2++;
			l[k2].a=a;
			l[k2].b=b;
		}
	}
	sort(h+1,h+k1+1,cmp1);
	sort(l+1,l+k2+1,cmp2);
	for(int i=1;i<=k1;i++)
	{
		if(r>=h[i].a)
		{
			r+=h[i].b;
			ans++;
		}
		else
		{
			break;
		}
	}
	memset(dp,0,sizeof(dp));
	dp[1][r]=ans;
	for(int i=1;i<=k2;i++)
	{
		for(int j=0;j<=r;j++)
		{
			if(j+l[i].b>=0&&j>=l[i].a)
			{
				dp[i+1][j+l[i].b]=max(dp[i+1][j+l[i].b],dp[i][j]+1);
			}
			dp[i+1][j]=max(dp[i+1][j],dp[i][j]);
		}
	}
	for(int i=0;i<=r;i++)
	{
		ans=max(ans,dp[k2+1][i]);
	}
	printf("%d\n",ans);
	return 0;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值