[题解]CF864E Fire
#1.0 简单分析
01背包的变式。约束条件由空间变为了时间,这里要注意到时间的一个特殊的性质:具有顺序。
首先,要进行一步很简单但很重要的步骤:排序。
那么为什么要排序呢?
显然。题目中给的顺序并不是时间顺序,而对于所有物品,我们应当先考虑烧毁时间较早的物品,原因很简单:每拯救一个物品需要消耗一定的时间。假如我们先考虑在时段 \(0\sim t\) 拯救烧毁时间较后的物品 A,接着,我们在考虑烧毁时间较前的物品 B,但我们并不知道之前 A 物品具体是在什么时候拯救的,如果要枚举,显然数据量太大了。
假设我们有两个物品 A,B,A 比 B 先烧毁,那么如果我们先考虑 A 得到的结果,一定不会比先考虑 B 得到的结果差,毕竟我们可以通过一些手段放弃之前的选择,而且可以更好地记录每个物品选取的时间。
注意到,我们还需要输出路径,那么可以增设状态,保存前驱,然后递归地遍历。
设计状态:
- \(f_{i,j}\) 表示当前考虑到第 \(i\) 个物品,拯救出的最后一个物品的拯救结束时间为 \(j\) 的最大收益。
- \(g_{i,j}\) —— 第 \(i-1\) 个物品时,拯救出的最后一个拯救结束时间为 \(g_{i,j}\) 。
状态转移: 可以直接按 01 背包进行转移。
还有一点要注意:
这里的答案并不像普通的 01 背包一般是 \(f_{n,T}\),而是 \(f_{n,1}\sim f_{n,T}\) 中的最大值,原因也很简单:我们设计的状态中的 \(j\) 是拯救出的最后一个物品的拯救结束时间,这个最后一个物品不一定是哪一个物品,自然会有不同的答案。
具体实现请见代码及注释。
#2.0 代码实现
const int N = 110;
const int M = 2010;
const int INF = 0x3fffffff;
struct T{
int t,d,p;
int ind;
};T a[N];
int n,f[N][M],g[N][M],maxn = -INF,ans = -INF;
int list[N],cnt,ans2;
inline int cmp(const T &x,const T &y){
return x.d < y.d;
}
inline int Max(const int &x,const int &y){
return x > y ? x : y;
}
inline void Path(int k,int t){ //递归遍历方案,并数清个数
if (k <= 0) return;
Path(k - 1,g[k][t]);
if (f[k][t] != f[k - 1][t]) //判断这个物品是否被选——f[i] 与 f[i-1] 是否相等
list[++ cnt] = k;
}
int main(){
scanf("%d",&n);
for (int i = 1;i <= n;i ++){
scanf("%d%d%d",&a[i].t,&a[i].d,&a[i].p);
a[i].ind = i;
}
sort(a + 1,a + 1 + n,cmp); //排序
for (int i = 1;i <= n;i ++){
for (int j = 1;j <= 2000;j ++) //继承
f[i][j] = f[i - 1][j],g[i][j] = j;
for (int j = a[i].d - 1;j >= a[i].t;j --)
if (f[i][j] < f[i - 1][j - a[i].t] + a[i].p){ //更新
f[i][j] = f[i - 1][j - a[i].t] + a[i].p;
g[i][j] = j - a[i].t;
}
}
for (int j = 1;j <= 2000;j ++) //找到最大答案
if (f[n][j] > ans) ans = f[n][j],ans2 = j;
Path(n,ans2); //统计
printf("%d\n%d\n",ans,cnt);
for (int i = 1;i <= cnt;i ++)
printf("%d ",a[list[i]].ind);
return 0;
}
END
希望能给您带来收获。