翰翰 1818 岁生日的时候,达达给她看了一个神奇的序列 A1,A2,…,ANA1,A2,…,AN。
她被允许从中选择不超过 MM 个连续的部分作为自己的生日礼物。
翰翰想要知道选择元素之和的最大值。
你能帮助她吗?
输入格式
第一行包含两个整数 N,MN,M。
第二行包含 NN 个整数 A1∼ANA1∼AN。
输出格式
输出一个整数,表示答案。
数据范围
1≤N,M≤1051≤N,M≤105,
|Ai|≤104|Ai|≤104输入样例:
5 2 2 -3 2 -1 2
输出样例:
5
解题思路:
将所有的正数和负数分开,即如图,绿色是整数,红色是负数;
共有两种选法
{
假设res为所有正数端的和, cnt为共选择了多少个正数段
1:每次选择所有的绿色,假设选择了cnt个所有的正数段(则k = cnt - m,
因为最多选择m个, k即为要从中减去的绿色的数量),
因此最终答案为即为res -= a[i]因为a[i] > 0所以可以写成res -= abs(a[i]),
因此题目转化为选出k尽量小的数,才能使得结果尽可能增大;
2:如图不能选择图中数字1的方法,因为要使得结果最大,所以只能选择数字2,不能选择图中的数字1,
因为1中包含负数要选负数的话只能和他后面的正数一起选择
若选择数字2,则我们可以将图中数字2中的3条边合为一条边;即权值为res -= abs(x);
}
所以题目最后就转化为了这题的解法:https://blog.csdn.net/qq_61935738/article/details/125703897?spm=1001.2014.3001.5501
代码:
#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];//数组a表示将序列变为正数数和负数分开的数组
int n, m;
bool st[N];//判断小根堆里的数是否已经用过
int l[N], r[N];//双链表的左右儿子;
void removes(int p)
{
r[l[p]] = r[p];//删除双链表中的左右儿子
l[r[p]] = l[p];
st[p] = true;//删除堆里的左右儿子
}
int main()
{
cin >> n >> m;
int k = 1;
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
if (!x) continue;//遇到0跳过,加不加0答案都一样;
if (a[k] * x < 0) a[++ k] = x;//将正数,负数分开
else a[k] += x;
}
n = k;
int cnt = 0, res = 0;
for (i = 1; i <= n; i ++ )//记录所有大于0的数字的数目,最后用k = cnt - m,即最后只需要每次循环k次
{
if (a[i] > 0)
{
cnt ++ ;
res += a[i];//最后答案:进行k次循环,假设每次取出的最小值为s[k],则最后的答案为res - s[k];
}
}
for (int i = 1; i <= n; i ++ )//建立双链表
{
l[i] = i - 1, r[i] = i + 1;
heap.push({abs(a[i])}, i);
}
while (cnt > m)//循环k次
{
while (st[heap.top().second]) heap.pop();//st表示已经被删掉的元素
auto t = heap.top()//每次取出最小值
int v = t.first, p = t.second;
if (l[p] != 0 && r[p] != n + 1 || a[p] > 0)//若a[p] < 0且在边界,则不能删除,就像图中数字一的情况
{
cnt -- ;
res -= v;
int left = l[p], right = r[p];
a[p] += a[left] + a[right];
heap.push({abs(a[p]), p});
removes(left);//删除左右儿子节点,将左右儿子节点和p节点融合为一个新节点
remover(right);
}
}
cout << res << endl;
return ;
}