题目
思路
做个🔨
这道题目,不愧对它是红色的,首先,我们先把连续的正数分到一块,连续的负数分到一块,连续的 0 0 0就根据左右数字的正负性归到左边的块和右边的块(但是绝对不允许单独成块,我就是在这个分块的地方调了好久,然后干脆直接用题解的思路了)。
然后这个时候我们就发现了很美妙的性质,数列是一个正一个负的排列下去,然后我们把所有正数的段全部选了,总共有 t t t个,但是只让我们选 m m m个,怎么办,对于多出的 t − m t-m t−m个,我们有两种方式让它消失。
- 删除一个正数段。
- 加入一个负数段。(其实就是合并这个负数段左右的数字)
对于1,2操作,其实都是让总和删除了 ∣ a i ∣ |a_i| ∣ai∣。
负数段中间总会隔一个正数段,正数段也是如此,所以单某个操作而言,我们不能选择相邻的段,但是难道两个操作一起就能选择相邻的段吗?
比如我1操作删除了1,然后2操作加入了2可以吗?可以可以个🔨,我们删除了
1
1
1,
2
2
2操作加入了
2
2
2就不能让
1
,
3
1,3
1,3合并而只是单纯的连在了
3
3
3上面,总块数并没有减少,所以这很明显是不行的。
所以我们只能选择不相邻的块进行操作,而每个操作其实都是删除 ∣ a i ∣ |a_i| ∣ai∣,所以这道题的子问题就是我们就是要选择 t − m t-m t−m个段让这些段的绝对值之和最小,参照P3620 【[APIO/CTSC 2007]数据备份】,然后总和删去即可。
当然注意几个细节。
- 对于 0 0 0和 c n t + 1 cnt+1 cnt+1(总块数+1)的位置,我们要把它的绝对值设置为无穷大,否则 0 0 0的后继节点的后悔操作会一直选择 0 0 0,并认为多选了一个点,实际上并没有,那这样会不会影响正确性呢?不会,如果你选了 1 1 1又后悔选了 2 2 2,那为什么当初不是直接选了 2 2 2呢?那不就说明 2 2 2的权值更大,那还选它干嘛。
- 对于 1 1 1和 c n t cnt 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;
}