[ TJOI 2013 ] 拯救小矮人

\(\\\)

\(Description\)


\(N\)个人,每个人有两个属性 \(A_i,B_i\) ,现在他们要从洞里逃出去。

\(x\) 个人能逃出去的条件是,随意选择一些还在洞里的人\((\)重新编号为\(1...k)\),满足

\[A_1+A_2+...+A_k+A_x+B_x\ge H \]

注意,第 \(x\) 个人不能在这 \(k\) 个人里。求最多能跑出去多少个人。

  • \(N\le 2000\ ,\ A_i,B_i,H\le10^5\)

\(\\\)

\(Solution\)


贪心和 \(DP\) 的结合,不得不说是一道好题。

首先考虑能否直接贪心。

考虑如果直接按照 \(A\) 从大到小排个序,然后强制留下来前面的一些使得满足出洞条件,然后让其他的人都出去是否是最优解。

并不是。如果把 \(A\) 比作身高,把 \(B\) 比作手长,那可能有一些身子又高手又长的人,用一些身子一般高但是手极短的人换掉,可能能逃跑的人更多。

\(\\\)

然后就有了比较厉害的 \(DP​\) 解法。

首先考虑两个人 \(x,y\) 他们如果要出洞,应该以什么顺序出。

答案是 \(A+B\) 较小的那个人先出。

证明分三种情况讨论。

  • 如果只能走一个,也就是说少了两人之一另一个都走不了,那谁都无所谓。
  • 如果都走不了,显然顺序无关。
  • 如果两个在相同的条件下都能走,那我们先让小的走,因为身子高手长的更好逃跑。

然后就可以按照 \(A+B\) 排序了。

\(\\\)

然后设状态 \(f[i]\) 表示 出去了 \(i\) 个人,剩下的人 的身高之和最多是多少。

然后状态之间的转移就代表出去了一个人。我们要保证转移合法,一定要满足:

  • 一个人不能出洞多次(在多次转移里使用同一个人)
  • 出洞的这个人一定在之前的集合里

然后解决这个问题的方法就比较妙了。

我们显然不能在外层枚举出去的人数,然后内层枚举出去的人,这样两个问题都无法避免。

采取一种类似背包的思路。外层枚举出去的人,内层枚举要更新的层数。

为了满足第一个限制条件,我们要像 \(01\) 背包那样更新,保证一个人只被使用一次。

为了满足第二个限制条件,我们要动态控制状态的上限。

\(\\\)

先放代码。

f[ans=0]=sum;
for(R int i=1;i<=n;++i){
  for(R int j=ans;j>=0;--j)
    if(f[j]+p[i].b>=m) f[j+1]=max(f[j+1],f[j]-p[i].a);
  if(f[ans+1]>=0) ++ans;
}

外层循环是考虑哪个人出去,内层循环是要更新的层数,内层倒序的原理上面说过了。

转移条件挺显然的吧,就是合法就把他拿出去,然后尝试更新。

为什么一定能拿出去?因为这个位置有值,一定是从更低的转移过来,而更低的拿出去的只是当前这个人之前的人,注意体会这个 \(ans\) 的作用。

\(\\\)

\(Code\)


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define N 2010
#define R register
#define gc getchar
#define inf 2000000000
using namespace std;

inline int rd(){
  int x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

int n,m,ans,f[N];

struct prs{int a,b;}p[N];

inline bool cmp(prs x,prs y){return x.a+x.b<y.a+y.b;}

int main(){
  n=rd();
  for(R int i=1;i<=n;++i){
    f[0]+=(p[i].a=rd());
    p[i].b=rd(); f[i]=-inf;
  }
  m=rd();
  sort(p+1,p+1+n,cmp);
  for(R int i=1;i<=n;++i){
    for(R int j=ans;j>=0;--j)
      if(f[j]+p[i].b>=m) f[j+1]=max(f[j+1],f[j]-p[i].a);
    if(f[ans+1]>=0) ++ans;
  }
  printf("%d\n",ans);
  return 0;
}

posted @ 2018-10-18 17:26  SGCollin  阅读(146)  评论(0编辑  收藏  举报