Codeforce 864E 需要优先处理的01背包

题意:

一个房子发生了火灾,给出 n n n个物品,每一行依次是 t i , d i , p i t_{i},d_{i},p_{i} ti,di,pi,分别代表拯救这个物品需要的时间,物品消失的时间(意味着完成拯救这个物品的时间 t i m e time time必须要 t i m e ≤ d i time\leq d_{i} timedi),这个物品的价值。求出能拯救的物品组合的最大价值,并给出拯救顺序,一次只能拯救一件物品。

思路:

时间的流逝是适合 d p dp dp的,因为决策可以依赖时间转移,每一个时间可以做出一个决策,设 f [ i ] f[i] f[i]为前 i i i分钟能拯救的物品组合的最大价值,拯救顺序选择用 v e c t o r vector vector存储,当 f [ i ] f[i] f[i]继承时,对应的 v e c t o r vector vector也继承,并且 p u s h _ b a c k push\_back push_back当前拯救的元素。那么如何转移呢?这显然是和 01 01 01背包类似的,倒序枚举时间,每个时间可以选择救也可以选择不救,但是显然每个时刻尽量选择救是最好的。所以状态转移方程为 f [ i ] = m a x ( f [ i ] , f [ i − t [ j ] ] + p [ j ] ) f[i]=max(f[i],f[i-t[j]]+p[j]) f[i]=max(f[i],f[it[j]]+p[j]),前提是开始的时间合法,完成拯救的时间也合法。其次,我们需要对物品排序,因为 d i d_{i} di最小的物品应该是最先枚举的,假设有这么个例子,输入数据是

2
3 8 2
3 4 1

我们先枚举第一件物品,使得 f [ 3 ] = 2 f[3]=2 f[3]=2,在第 3 3 3秒拯救了第一件物品,第二件物品枚举时无法接在 3 3 3秒之后,因为第 3 3 3秒已经消失了。而排序完先枚举第二个物品, f [ 3 ] = 1 f[3]=1 f[3]=1,此时第一件物品枚举时, f [ 6 ] f[6] f[6]可以有 f [ 3 ] f[3] f[3]转移来,每次转移是接在之前救完的后面,我们让时间宽裕的尽量在后面拯救,这样才能获得最大值。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll read()
{
	ll ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<3)+(ret<<1)+ch-48;
		ch=getchar();
	}
	return ret*base;
}

int n,t[105],d[105],p[105],max1,dp[2005],ans,a[2005];
vector<int>v[2005];

bool cmp(int a,int b)
{
	return d[a]<d[b];
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=i;
		t[i]=read();
		d[i]=read();
		p[i]=read();
		max1=max(max1,d[i]);
	}
	sort(a+1,a+1+n,cmp);
//	for(int i=1;i<=n;i++) printf("%d ",a[i]);
//	cout<<endl;
	for(int i=1;i<=n;i++)
	{
		if(t[a[i]]<d[a[i]])
		{
			for(int j=max1;j>=1;j--)
			{
				if(j<d[a[i]]&&j-t[a[i]]+1>=1&&dp[j-t[a[i]]]+p[a[i]]>dp[j])//要救现在的这个物品
				{
					dp[j]=dp[j-t[a[i]]]+p[a[i]];
					v[j]=v[j-t[a[i]]];
					v[j].push_back(a[i]);
				}
			}
		}
	}
//	for(int i=1;i<=max1;i++) printf("dp[%d]=%d\n",i,dp[i]);
	int pos;
	for(int i=1;i<=max1;i++)
	{
		if(dp[i]>ans)
		{
			ans=dp[i];
			pos=i;	
		}
	}
	cout<<ans<<endl;
	printf("%d\n",v[pos].size());
	for(int i:v[pos]) printf("%d ",i);
 	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值