题意:
一个房子发生了火灾,给出 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} time≤di),这个物品的价值。求出能拯救的物品组合的最大价值,并给出拯救顺序,一次只能拯救一件物品。
思路:
时间的流逝是适合 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[i−t[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;
}