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;
}
标签:
解题报告
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现