P4823 [TJOI2013] 拯救小矮人
感觉这个题的操作很新奇,做个记录。
大概题面:
一群小矮人掉进了一个很深的陷阱里,由于太矮爬不上来,于是他们决
定搭一个人梯。即:一个小矮人站在另一小矮人的肩膀上,直到最顶端
的小矮人伸直胳膊可以碰到陷阱口。
对于每一个小矮人,我们知道他从脚到肩膀的高度 \(A_i\),并且他的胳膊长
度为 \(B_i\)。陷阱深度为 \(H\)。
如果我们利用矮人 \(1\),矮人 \(2\),矮人 \(3\),\(\ldots\),矮人 \(k\) 搭一个梯子,满足
\(A_1 + A_2 + A_3 + \ldots + A_k + B_k \geq H\),那么矮人 \(k\) 就可以离开陷阱逃跑了,
一旦一个矮人逃跑了,他就不能再搭人梯了。
我们希望尽可能多的小矮人逃跑,问最多可以使多少个小矮人逃跑。
数据范围:\(1 \leq N\leq 2000\),\(1 \leq A_i,B_i,H\leq10^5\)。
1:贪心+dp
首先小矮人出去一定与顺序有关,前一个小矮人出去可能导致后面小矮人无法出去。
那么从贪心的角度思考,将小矮人按 \(a_i+b_i\) 从小到大排序,
可以对比,当 \(a_i+b_i < a_j+b_j\) 时,\(H+a_i+b_i\) 与 \(H+a_j+b_j\) 那个更优? (*交换对比)
发现当 \(a_i+b_i\) 先出去时,其他小矮人更有机会出去,而 \(a_j+b_j\) 先出去时,其他小矮人可能够不到而无法出去。
当 \(a_i+b_i = a_j+b_j\) 时,显然 \(a_i\) 较大的人应该留下(让人梯更长)。
我们发现 \(N\) 较小,可以枚举考虑到每个人的状态,考虑dp。
接下来考虑如何dp。很自然的想到有 \(f_{i,j}=x\) 为枚举第 \(i\) 个小矮人时,人梯长度为 \(j\) 时,最多能逃出 \(x\) 个人。 (我好菜...)
发现 \(j\) 很大,不好转移。所以交换一下,改成:
\(f_{i,j}=x\) 为枚举第 \(i\) 个小矮人时,能逃出 \(j\) 个人时,人梯长度最大为 \(x\) 。 (*枚举信息交换)
考虑转移,当有 \(f_{i-1,j-1}+b_i \geq H\) 时,小矮人 \(i\) 可以逃出。
则此时有 \(f_{i,j}=max(f_{i,j},f_{i-1,j-1}-a_i)\)
我们发现 \(f_{i,j}\) 在转移时只与上一位 \(i-1\) 有关,所以考虑滚动数组将 \(i\) 这一维优化掉。 (*滚动数组)
则:\(f_j=max(f_j , f_{j-1}-a_i)\)
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=2005;
int n,h;
struct node{
int x,y;
bool operator<(const node& p)const{
if(x+y==p.x+p.y)return x<p.x;
else return x+y<p.x+p.y;
}
}a[N];
int f[N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+1+n);
scanf("%d",&h);
memset(f,-0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++)f[0]+=a[i].x;
for(int i=1;i<=n;i++){
for(int j=n;j>=1;j--){
if(f[j-1]+a[i].y>=h){
f[j]=max(f[j],f[j-1]-a[i].x);
}
}
}
for(int i=n;i>=0;i--){
if(f[i]>=0){
printf("%d\n",i);
break;
}
}
return 0;
}
这个题目也可以用反悔贪心,(有时间就写在别的地方,补链接)
2:我学会了:
- 考虑顺序。并且可以通过交换在对比知道谁应该在前面。
- 有时不好枚举时可以交换信息