【k个最大连续子区间和】 生日礼物

传送门

题意

给定长度为\(n\)的序列\(A_{1}、A_{2} \dots A_{n}\),从中选不超过\(m\)个连续的部分使得它们的和最大

数据范围

\(\begin{array}{l}1 \leq N, M \leq 10^{5} \\ \left|A_{i}\right| \leq 10^{4}\end{array}\)

题解

因为选择的线段必须要是连续的,所以输入过程直接把符号相同的区间合并为一个点,
这样整个序列就变成了正负交替的序列
计算出所有正数之和以及正数的个数\(cnt\)

  • 如果\(cnt>m\),选\(k=cnt-m\)处使区间变为\(m\)
  • 否则不需要做任何操作,和即最大值

将所有数的绝对值放在堆,每次选择最小的

  • 如果是正数,代表将当前的区间和左右区间合并为一个,下次如果想要减少只能是两个正的合并
  • 如果是负数
    • 不在整个序列的两段直接减去,代表和左右两遍的正数合并了,
    • 序列两端,负数绝对值最小且在两端点不能将两个正数区间合并,并没有实际减少连续区间的个数

Code

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<n;i++)
#define per(i,a,n) for(int i=n-1;i>=a;i--)
#define pb push_back 
#define fi first
#define se second 
typedef pair<int,int> pii;
const int N=1e5+10;
int n,m;
int a[N];
bool st[N];
int l[N],r[N];
void remove(int i){
    r[l[i]]=r[i];
    l[r[i]]=l[i];
    st[i]=true;
}
int main(){
    priority_queue<pii,vector<pii>,greater<pii>>q;
    scanf("%d%d",&n,&m);
    int idx=1;
    rep(i,0,n){
        int x;
        scanf("%d",&x);
        if(1ll * x * a[idx] < 0)
            a[++idx]=x;
        else
            a[idx]+=x;
    }
    n=idx;
    int cnt=0,sum=0;
    rep(i,1,n+1){
        if(a[i] > 0){
            cnt++;
            sum+=a[i];
        }
        l[i]=i-1;
        r[i]=i+1;
        q.push({abs(a[i]),i});
    }

    while(cnt > m){
        while(st[q.top().se]) q.pop();

        auto t=q.top();
        q.pop();
        int val=t.fi,idx=t.se;
        if( (l[idx]!=0 && r[idx]!=n+1) || a[idx] > 0){
            cnt--;
            sum-=val;
            int left=l[idx],right=r[idx];
            a[idx] += a[left]+a[right];
            q.push({abs(a[idx]),idx});
            remove(left);remove(right);
        }
    }
    printf("%d\n",sum);
}

posted @ 2020-06-10 13:20  Hyx'  阅读(198)  评论(0)    收藏  举报