生日礼物

题目

题目

思路

做个🔨

这道题目,不愧对它是红色的,首先,我们先把连续的正数分到一块,连续的负数分到一块,连续的\(0\)就根据左右数字的正负性归到左边的块和右边的块(但是绝对不允许单独成块,我就是在这个分块的地方调了好久,然后干脆直接用题解的思路了)。

然后这个时候我们就发现了很美妙的性质,数列是一个正一个负的排列下去,然后我们把所有正数的段全部选了,总共有\(t\)个,但是只让我们选\(m\)个,怎么办,对于多出的\(t-m\)个,我们有两种方式让它消失。

  1. 删除一个正数段。
  2. 加入一个负数段。(其实就是合并这个负数段左右的数字)

对于1,2操作,其实都是让总和删除了\(|a_i|\)

负数段中间总会隔一个正数段,正数段也是如此,所以单某个操作而言,我们不能选择相邻的段,但是难道两个操作一起就能选择相邻的段吗?

比如我1操作删除了1,然后2操作加入了2可以吗?可以可以个🔨,我们删除了\(1\)\(2\)操作加入了\(2\)就不能让\(1,3\)合并而只是单纯的连在了\(3\)上面,总块数并没有减少,所以这很明显是不行的。

所以我们只能选择不相邻的块进行操作,而每个操作其实都是删除\(|a_i|\),所以这道题的子问题就是我们就是要选择\(t-m\)个段让这些段的绝对值之和最小,参照P3620 【[APIO/CTSC 2007]数据备份】,然后总和删去即可。

当然注意几个细节。

  1. 对于\(0\)\(cnt+1\)(总块数+1)的位置,我们要把它的绝对值设置为无穷大,否则\(0\)的后继节点的后悔操作会一直选择\(0\),并认为多选了一个点,实际上并没有,那这样会不会影响正确性呢?不会,如果你选了\(1\)又后悔选了\(2\),那为什么当初不是直接选了\(2\)呢?那不就说明\(2\)的权值更大,那还选它干嘛。
  2. 对于\(1\)\(cnt\)是负数时,也要把绝对值设置为无穷大,因为加入这两段并不会让总块数减少。

代码

#include<cstdio>
#include<cstring>
#include<queue>
#define  N  110000
using  namespace  std;
typedef  pair<int,int>  Pint;
int  n,m,a[N],k/*表示选取k个数字*/,st[N]/*不能选择的数字*/,val[N],cnt/*表示分别有几段数字*/,sum;
struct  node//链表 
{
	int  l,r,x;
}li[N];
priority_queue<Pint ,vector<Pint>,greater<Pint> >p;
void  del(int  x)/*表示把x的左右点删除*/
{
	st[li[x].l]=1;st[li[x].r]=1;
	li[x].l=li[li[x].l].l;li[x].r=li[li[x].r].r;
	li[li[x].l].r=x;li[li[x].r].l=x;
}
inline  int  zbs(int  x){return  x<0?-x:x;}
int  main()
{
//	freopen("gift2.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int  i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if((long  long)a[i]*val[cnt]>0/*同号判断*/  ||  a[i]==0)val[cnt]+=a[i];//判断成块,这样判断能够把0拉到前面最近的符号块中。
		else  val[++cnt]=a[i]; 
	}
	if(val[cnt]<0)val[cnt]=0,cnt--;
	if(val[1]<0)val[1]=-999999999;
	li[0].x=li[cnt+1].x=999999999;
	//细节
	int  tot=0;
	for(int  i=1;i<=cnt;i++)
	{
		li[i].l=i-1;li[i].r=i+1;li[i].x=zbs(val[i]);
		p.push(make_pair(zbs(val[i]),i));
		if(val[i]>0)sum+=val[i],tot++;
	}
	if(tot<=m)printf("%d\n",sum);
	else
	{
		k=tot-m;
		while(k)
		{
			Pint  x=p.top();p.pop();
			if(st[x.second])continue;
			sum-=x.first;
			Pint  now=x;
			now.first=li[li[x.second].l].x+li[li[x.second].r].x-x.first;p.push(now);//堆的修改 
			li[now.second].x=now.first;del(now.second);//链表的修改 
			k--;
		}
		printf("%d\n",sum);
	}
	return  0;
}
posted @ 2020-07-28 09:57  敌敌畏58  阅读(92)  评论(0编辑  收藏  举报