题解 P6821 [PA2012] Tanie linie

本来想写 wqs 二分来着,然后推不出状态转移方程,摆烂了。


题目描述

给定含 \(n\) 个数的序列,求至多 \(k\) 个不相交子段的和的最大值。

具体思路

由于选 \(k\) 堆连续的数,因此一堆连续的符号相同的数,只有可能是同时被选或者同时不被选。

因此我们先对原序列预处理一遍,将相同符号的合并到一起。

现在的序列就是一个正负交替的序列。

要答案最大,那我们肯定先把所有正数都选出来。

若正数个数小于等于 \(k\),那我们直接输出就好了,因为再选一些负数只会让我们的答案变得更小。

若正数个数大于 \(k\),此时我们有两种操作。

  • 操作 1:将选定的正数中删除一部分正数。

  • 操作 2:将两堆正数连同它们之间的负数合并成一堆,这样堆数就会减少一堆。

对于操作 1,相当于减少 \(a_i\),对于操作 2,相当于减少了 \(-a_i\),因此可以看成减少了 \(\left | a_i \right |\)

我们要让结果最大,就是要让删除的数最小,因此用一个二叉堆来给 \(\left | a_i \right |\) 排序,而合并操作显然是用链表来维护的。

Code

#include<cstdio>
#include<queue>
#include<cmath>
using namespace std;
typedef long long LL;
typedef pair<LL,int>PII;
const int N=1e6+5;
const LL inf=1e18;
int L[N],R[N];
LL a[N];int v[N];
int n,m,len=1;
priority_queue<PII,vector<PII>,greater<PII>>Q;
void ins(int x){
    L[x]=x-1;
    R[x]=x+1;
}
void del(int x){
	R[L[x]]=R[x];
	L[R[x]]=L[x];
	v[x]=1;
}
bool check(int x){
	if((0<L[x]&&R[x]<n+1)||a[x]>0)
		return true;
	return false;
}
bool check1(LL x,LL y){
	if((x>=0&&y>=0)||(x<0&&y<0))
		return true;
	return false;
}
int main(){
	scanf("%d%d",&n,&m);
	scanf("%lld",&a[len]);
    for(int i=2;i<=n;i++){
    	LL x;scanf("%lld",&x);
    	if(!x)continue;
    	if(!check1(a[len],x))a[++len]=x;
    	else a[len]=a[len]+x;
	}
	n=len;
	LL ans=0;int cnt=0;
    for(int i=1;i<=n;i++){
    	if(a[i]>0){
    		ans=ans+a[i];
    		cnt++;
		}
        ins(i);
        Q.push({abs(a[i]),i});
    }
    while(cnt>m){
    	while(v[Q.top().second])Q.pop();
    	PII x=Q.top();Q.pop();
    	if(check(x.second)){
    		ans=ans-x.first;cnt--;
    		int l=L[x.second],r=R[x.second];
    		a[x.second]+=a[l]+a[r];
    		Q.push({abs(a[x.second]),x.second});
    		del(l),del(r);
		}
    }
    printf("%lld",ans);
    return 0;
}
posted @ 2023-09-18 10:56  reclusive2007  阅读(43)  评论(0)    收藏  举报