• 可撤销贪心


    可撤销贪心

    概念

    • 可撤销贪心一般是在一轮贪心后,在删除原来的数之后,再加入一个数,如果要选这个数,就要算上撤销一轮操作的代价(这个代价可能为负)。

    经典例题

    • 种树

      • 题目大意:圆形广场共有 N 个种树的位置,顺时针编号1N。并且每个位置都有一个美观度 (a_i) ,两株树不能种在相邻的位置(1号和N号也算相邻位置)一共有 M 株树,现在小D也想知道应该如何摆这 N 株树才能使美观度最大。
    • 我们可以贪心,每一次从剩下可选的数中选出一个最大的 (a_i),但这样显然是错的,因为我们可能选择(a_{i-1})(a_{i+1}) 会更优 。那么怎么办呢?我们可以考虑让贪心变得可撤销。

      • 假设当前我们选了第i个数,那么它的贡献就是 (a_i) 。但是最优解有可能不选它,如果不选它的话,那么就会选它的两侧相邻的点,如果这样选的话贡献就是 (a_{i-1}+a_{i+1}) 。要把之前的贪心撤消了,就相当于把ans加上 (a_{i-1}+a_{i+1}-a_i) 。新加的点其相邻关系也发生了变化,所有我们要用两个数组来记录点的相邻关系,具体操作见下。
    • 操作步骤:

      1. 用一个大根堆维护节点的观赏度。
      2. 取出堆顶((i,A_i)) ,同时把i相邻的两个位置种树的价值-(A_i) 作为一个新的点加入堆,同时更新新点的左右相邻点坐标。
        • (A_i) 有可能表示种一棵树的观赏度,也有可能是种多棵树的观赏度,主要跟合并次数有关。
        • 新产生的节点减去(A_i) 是为了撤销已经在前面选了(A_i) 的决定。
        • 新产生的点必须要在节点i的两边都种上树,因为新点包含了撤销了一次的操作,可以认为在当次的操作中选了一个以前未种树的地方种树,同时把和它相邻的已经种的树挪到了另一边未种树的地方。
        • 新添加的点的相邻关系发生了变化,此时要更新新点的相邻关系。
        • 取出堆顶,说明选择了在堆顶种树,那相邻的两个点就不能种树了,所以要对相邻点进行标记。
      3. 重复操作2直到M棵树均种完。
    • Code

      #include <bits/stdc++.h>
      const int maxn=2e5+5;
      struct Node{
          int id,w;
          Node(){};
          Node(int x,int y){id=x;w=y;}
          bool operator <(const Node &a)const{
              return w<a.w;//大根堆
          } 
      };
      int L[maxn],R[maxn],flag[maxn],a[maxn],n,m,ans=0;
      std::priority_queue <Node> q;
      void Solve(){
          scanf("%d%d",&n,&m);
          if(n<m*2){//显然
              printf("Error!
      ");return;
          }
          for(int i=0;i<n;++i){//小标0~n-1,便于处理环形
              scanf("%d",&a[i]);
              q.push(Node(i,a[i]));
              L[i]=(i-1+n)%n;//i的左邻,加n是避免-1,0的左邻是n-1
              R[i]=(i+1)%n;//i的右邻n-1的右邻为0
          }
          for(int i=1;i<=m;++i){//种m棵树
              Node t=q.top(); q.pop();
              int j=t.id;
              if(flag[j]){//如果j的邻居已经种了树了
                  --i; continue; //此次操作并没有种树所以--i
              }
              ans+=t.w;//选择在j处种树
              flag[L[j]]=1; flag[R[j]]=1;//把j的邻居标记上,不能种树
              a[j]=a[L[j]]+a[R[j]]-t.w;//产生新点,j两边种树,j就不能种树
              q.push(Node(j,a[j]));//新点进堆
              R[L[L[j]]]=j; L[R[R[j]]]=j;//更新新点的左右邻居和j的关系  
              L[j]=L[L[j]]; R[j]=R[R[j]];//更新新点j的左右邻居
          }
          printf("%d
      ",ans);
      }
      int main(){
         Solve();
         return 0;
      }
      
  • 相关阅读:
    Java Web 047: 处理商品列表的查询
    Java Web 047:开发商品列表的模板页
    Java Web 046: 处理登录成功后的跳转
    Java Web 045: 处理登录请求
    Java Web 044: 处理注册响应
    Java Web 043: 处理注册请求
    Java Web 042: 创建UserDao控制user的相关数据库操作
    Java Web 041: 创建数据模型和模拟数据库
    Java Web 03: MVC分层架构 / JavaEE分层架构 (图解)
    Java Web 02: 单例模式
  • 原文地址:https://www.cnblogs.com/hbhszxyb/p/13142056.html
Copyright © 2020-2023  润新知