Luogu P6821 [PA2012] Tanie linie 题解

Solution

自己手造几个样例,可以发现,一段连续的正数或负数一定同时选或不选。这是因为只选连续的正数段一定是最优的,如果要选负数一定是因为正数段个数太多,要通过选负数将两个正数段合并以减少段数。因此,可以先将连续的正数和连续的负数合并。这样一来,原来的序列变成了正负交替的新序列。

如果新序列正数的个数小于等于 \(k\),那么选上所有的正数一定是最优的,直接输出所有的正数的和即可。

如果正数的个数大于 \(k\),那么我们可以先将全部正数选出,再删除或合并一些段使段数减少到 \(k\)。设所有正数的和为 \(sum\),一共有 \(k_1\) 段。此时我们有两种选择:

1.删除一段正数 \(x\),则 \(sum\) 会减少 \(x\)

2.将两段正数合并,则 \(sum\) 会加上中间一段负数 \(x\),也就是减少 \(|x|\)

两种操作都会使 \(k_1\) 减少 \(1\),因此一共要进行 \(k_1-k\) 次操作。显然我们的目标是让每次操作 \(sum\) 减少的值尽量小,可以使用一个小根堆维护最小值。初始时先把每个数的绝对值插入小根堆,每次取出堆顶元素进行操作。但是要注意选完最小值之后原序列会发生合并,要用链表维护原序列,每次操作后将 a[i]+a[l[i]]+a[r[i]] 插入二叉堆,并删除 a[l[i]]a[r[i]]。其中 a[l[i]] 表示 a[i] 的前驱,a[r[i]] 表示 a[i] 的后继。

Code

#include<bits/stdc++.h>
#define LL long long
#define PLI pair<LL,int>
using namespace std;
const int N=1e6+10;
int n,m;
struct list{
	int l,r;
	LL x;
}a[N];
priority_queue<PLI,vector<PLI>,greater<PLI>> q;
void del(int x)
{
	a[a[x].l].r=a[x].r;
	a[a[x].r].l=a[x].l;
	a[x].x=0;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i].x),a[i].l=i-1,a[i].r=i+1;
	for(int i=2;i<=n;i++)
	{
		if((a[i].x>0&&a[a[i].l].x>0)||(a[i].x<0&&a[a[i].l].x<0)||!a[i].x)
		{
			a[i].x+=a[a[i].l].x;
			del(a[i].l);
		}
	}
	int cnt=0;
	LL ans=0;
	for(int i=1;i<=n;i++)
		if(a[i].x>0) 
		{
			ans+=a[i].x;
			cnt++; 
		}
	if(cnt<=m) printf("%lld\n",ans),exit(0);
	for(int i=1;i<=n;i++)
		if(a[i].x)
			q.push({abs(a[i].x),i});
	while(cnt>m)
	{
		while(!a[q.top().second].x) q.pop();
		LL x=q.top().first;int i=q.top().second;q.pop();
		if(!a[i].x) continue;
		if(a[i].x>0||(a[i].l&&a[i].r!=n+1))
		{
			ans-=x;cnt--;
			a[i].x+=a[a[i].l].x+a[a[i].r].x;
			del(a[i].l),del(a[i].r);
			q.push({abs(a[i].x),i});
		}
	}
	printf("%lld\n",ans);
	return 0;
}
posted @   __Star_Sky  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示