【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);
}

浙公网安备 33010602011771号