【贪心+堆+链表】51Nod1053[最大M子段和 V2]题解

17 篇文章 0 订阅
11 篇文章 0 订阅

题目概述

{An} 中选至多 m 个不相交的子段,求子段和的最大值。

解题报告

初始想法是DP,好像可以优化到 O(n2) ,但是对于 50000 的数据范围还是不行。

0 无视掉,我们就可以把 {An} 分为一系列正子段和负子段(正负交替),选走所有正段肯定是最优秀的,但是正段个数可能大于 m ,所以我们要想办法使块数减少(定义 V(i) 表示 i 段权值):

  1. 选一个不在两端的负段 i ,使左右两边的正段与其合并,块数减少 1 ,子段和减少 |V(i)|

    • 选一个正段 i ,将其删除,并使左右两边的负段与其合并(贪心),块数减少 1 ,子段和减少 |V(i)|
    • 我们会发现子段和都是减少 |V(i)| ,所以我们建一个堆,刚开始将所有段放入,每次挑出绝对值最小的段进行处理,直到当前段数小于等于 m <script type="math/tex" id="MathJax-Element-17">m</script> 就可以得出最优解。

      示例程序

      #include<cstdio>
      #include<algorithm>
      using namespace std;
      typedef long long LL;
      const int maxn=50000;
      
      int n,m,now,l[maxn+5],r[maxn+5];
      LL v[maxn+5],ans;
      int si,Heap[maxn+5];
      
      inline LL absl(LL x) {if (x<0) return -x; else return x;}
      inline bool cmp(int a,int b) {return absl(v[a])>absl(v[b]);}
      inline void Push(int x) {Heap[++si]=x;push_heap(Heap+1,Heap+1+si,cmp);}
      inline void Pop() {pop_heap(Heap+1,Heap+1+si--,cmp);}
      inline void Merge(int L,int p,int R)
      {
          v[p]+=v[L]+v[R];L=l[L];R=r[R];
          l[p]=L;r[L]=p;l[R]=p;r[p]=R;
      }
      int main()
      {
          freopen("program.in","r",stdin);
          freopen("program.out","w",stdout);
          int N;scanf("%d%d",&N,&m);
          for (int i=1,x,f=0;i<=N;i++)
          {
              scanf("%d",&x);if (!x) continue;
              if (!f) {if (x<0) f=-1; else f=1;n++;}
              if (x*f>0) v[n]+=x; else {if (x<0) f=-1; else f=1;v[++n]=x;}
          }
          l[0]=0;r[0]=1;l[n+1]=n;r[n+1]=n+1;
          for (int i=1;i<=n;i++)
          {
              l[i]=i-1;r[i]=i+1;Push(i);
              if (v[i]>0) now++,ans+=v[i];
          }
          while (now>m)
          {
              int p=Heap[1],lp=l[p],rp=r[p];Pop();
              if (r[lp]!=p||l[rp]!=p) continue;
              if ((v[p]<0&&(lp<1||n<rp))||!v[p]) continue;
              ans-=absl(v[p]);now--;Merge(lp,p,rp);Push(p);
          }
          return printf("%lld\n",ans),0;
      }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值