N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。
例如:-2 11 -4 13 -5 6 -2,分为2段,11 -4 13一段,6一段,和为26。
(2 <= N , M <= 50000)
分析
需要猜一个结论:最大M子段和,必然由最大M+1子段和,通过删除一个子段,或将两个子段合成一个构造而成。
证明不会….
然后就可以用链表+堆做了。
但一个奇怪的发现是:这个算法的后半部分等价于将所有正数取负,把相邻的负数缩成一个,然后在序列中选取m个不相邻元素的最大值…这两个问题莫不是什么对偶之类的…
代码
#include <bits/stdc++.h>
#define mp std::make_pair
#define f first
#define s second
#define pair std::pair
typedef long long ll;
const int N = 50010;
int read()
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}
int n,m,now;
int l[N * 4],r[N * 4];
ll sum[N];
int a[N];
ll tab[N * 4];
int top;
int use[N * 4];
std::priority_queue<pair<ll, int> > Q;
int main()
{
n = read(), m = read();
for (int i = 1; i <= n; i++)
a[i] = read(), sum[i] = a[i] + sum[i - 1];
int last = 0, cnt = 0;
ll ans = 0;
for (int i = 1; i <= n; i++)
{
if (a[i] >= 0)
{
cnt++;
if (last != 0)
tab[++top] = sum[i - 1] - sum[last];
ans += a[i];
tab[++top] = -a[i];
last = i;
}
}
if (cnt <= m)
{
printf("%lld\n",ans);
return 0;
}
for (int i = 1; i <= top; i++)
l[i] = i - 1, r[i] = i + 1;
r[top] = 0;
for (int i = 1; i <= top; i++)
Q.push(mp(tab[i], i));
for (int i = cnt; i > m; i--)
{
while (use[Q.top().s])
Q.pop();
pair<ll, int> p = Q.top();
Q.pop();
ans += p.f;
int id = p.s;
use[id] = 1;
if (!l[id] || !r[id])
{
if (!l[id])
use[r[id]] = 1, l[r[r[id]]] = 0;
else use[l[id]] = 1, r[l[l[id]]] = 0;
}
else
{
int L = l[id], R = r[id];
tab[++top] = tab[L] + tab[R] - tab[id];
use[L] = use[R] = 1;
r[l[L]] = l[r[R]] = top;
l[top] = l[L], r[top] = r[R];
Q.push(mp(tab[top], top));
}
}
printf("%lld\n",ans);
}