• [ TJOI 2013 ] 拯救小矮人


    (\)

    (Description)


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

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

    [A_1+A_2+...+A_k+A_x+B_xge H ]

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

    • (Nle 2000 , A_i,B_i,Hle10^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
    ",ans);
      return 0;
    }
    
    
  • 相关阅读:
    [转]protobuf的编译安装
    [转]OpenMP中几个容易混淆的函数(线程数量/线程ID/线程最大数)以及并行区域线程数量的确定
    C++类中静态数据成员MAP如何初始化
    [转]gcc -ffunction-sections -fdata-sections -Wl,–gc-sections 参数详解
    的机器学习开源工具分享
    机器学习常见的采样方法
    图像配准与深度学习方法
    卷积网络中的几何学你了解多少?
    云计算、虚拟化和容器
    在数据科学领域,你掌握这个24个python库就够了!
  • 原文地址:https://www.cnblogs.com/SGCollin/p/9811832.html
Copyright © 2020-2023  润新知