《算法竞赛进阶指南》生日礼物

生日礼物

翰翰18岁生日的时候,达达给她看了一个神奇的序列 A1,A2,…,AN。

她被允许从中选择不超过 M 个连续的部分作为自己的生日礼物。

翰翰想要知道选择元素之和的最大值。

你能帮助她吗?

输入格式
第一行包含两个整数N,M。

第二行包含N个整数A1~AN。

输出格式
输出一个整数,表示答案。

数据范围
1≤N,M≤105,
|Ai|≤104
输入样例:
5 2
2 -3 2 -1 2
输出样例:
5

本题的解决方法是链表,即我们首先选择所有的正数序列作为最大值,然后在判断是否超过所要求的序列的个数,如果没有那么这就是要求的最大值,如果超过了我们就需要用贪心的方法去将这些已经得到的正数序列中“排除”一部分。

在判断上述情况是我们需要先预处理一下原始数据,即我们将连续的正数或负数序列合并成一点,这是因为我们如果选择当前的某一个正数的话为了求取整体值最大化我们是一定会把它周围的所有正数都选择上的;还有如果我们要选择一个负数时,一定是因为在当前负数所在的序列的左右两侧的正数需要合并我们才需要选择当前负数,而如果是这样的话当前的负数序列也是会被全部选择上的。所以我们需要将同符号的值合并成一个点,这样预处理之后的序列一定会变成一正一负的序列:我们就可以再次基础之上先选择所有的正数作为最大值然后在根据情况是否需要在去除一些正数。

“排除”的方法有两种:一种是我们直接在将其中一个正数点去除就行,这样相当于在原始序列中去掉了一个序列;还有一种方法就是合并两个正数点,实质是将两个正数和中间的负数整体合并成一点,这在原始数据中相当于两个序列变成一个序列,也相当于去掉一个序列。

我们可以在这两种“排除”方法中找到规律的:第一种虽然它只是将自己去掉了,但它还是会影响到它左右两边的负数点的,因为在此之后可能会有合并操作“经过”当前点,而这是不在是经过当前点了而是肯定经过当前点以及左右点共三点,那么我们就需要进行合并操作提前合并成一点。

这样第一种和第二中的“排除”方法都统一了只要确定要去掉当前点那么就需要合并当前点以及左右点共三点。

然后我们在考虑具体的值在第一种“排除”方法中每次的数值是res(总值)=res- 排除值

第二种方法是res=res+(排除值),此时的排除值是负数所以是加上。

综上所述,其实都可以表示为:res=res-|排除值|,即我们可以将所有值的绝对值放到小根堆中进行遍历查找。

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const  int N=100010;
typedef pair<int ,int > PII;
int a[N];
int l[N],r[N];//链表中标记左右位置的数组
int n,m;
bool st[N];//当弹出某一位置元素时,可能会影响到其他位置的元素的选择情况
void remove(int p)
{
	l[r[p]]=l[p];
	r[l[p]]=r[p];
	st[p]=true;
}
int main()
{
	cin>>n>>m;
	int k=1;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		if((long long )a[k]*x<0)a[++k]=x;//将相邻且相同符号的元素合并
		else a[k]+=x;
	}
    
    int cnt=0,res=0;
    priority_queue<PII,vector<PII > ,greater<PII > >heap;
    n=k;//将合并后的边界更新
    for(int i=1;i<=k;i++)
    {
    	l[i]=i-1;
    	r[i]=i+1;//初始化相邻数组
        if(a[i]>0)
        {//首先我们得到所有正数的和,即最大值
        	cnt++;
        	res+=a[i];
        }
        heap.push({abs(a[i]),i});
    }

    while(cnt>m)
    {//当我们选中的所有正数的个数(即连续的正数序列的个数)超过要求时
    	//我们就要在其中有选择的去除一些
    	while(st[heap.top().second])heap.pop();
    	//当我们堆顶元素位置被标记为删除时,我们在小顶堆中将其删除
    	auto t=heap.top();
    	heap.pop();//我们获得当前绝对值最小的元素,将其删除

        int v=t.first,p=t.second;
        int left=l[p],right=r[p];
        if(left>0&&right<n+1||a[p]>0)
        {
        	cnt--;
        	res-=v;
        	a[p]+=a[left]+a[right];
        	remove(left);
        	remove(right);
        	heap.push({abs(a[p]),p});
        }
    }
   cout<<res<<endl;
   return 0;
}
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:上身试试 返回首页