题解 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;
}

浙公网安备 33010602011771号