题目概述
在 {An} 中选至多 m 个不相交的子段,求子段和的最大值。
解题报告
初始想法是DP,好像可以优化到
把
0
无视掉,我们就可以把
- 选一个不在两端的负段
i ,使左右两边的正段与其合并,块数减少 1 ,子段和减少|V(i)| 。- 选一个正段
i
,将其删除,并使左右两边的负段与其合并(贪心),块数减少
1 ,子段和减少 |V(i)| 。 我们会发现子段和都是减少 |V(i)| ,所以我们建一个堆,刚开始将所有段放入,每次挑出绝对值最小的段进行处理,直到当前段数小于等于 m <script type="math/tex" id="MathJax-Element-17">m</script> 就可以得出最优解。
示例程序
#include<cstdio> #include<algorithm> using namespace std; typedef long long LL; const int maxn=50000; int n,m,now,l[maxn+5],r[maxn+5]; LL v[maxn+5],ans; int si,Heap[maxn+5]; inline LL absl(LL x) {if (x<0) return -x; else return x;} inline bool cmp(int a,int b) {return absl(v[a])>absl(v[b]);} inline void Push(int x) {Heap[++si]=x;push_heap(Heap+1,Heap+1+si,cmp);} inline void Pop() {pop_heap(Heap+1,Heap+1+si--,cmp);} inline void Merge(int L,int p,int R) { v[p]+=v[L]+v[R];L=l[L];R=r[R]; l[p]=L;r[L]=p;l[R]=p;r[p]=R; } int main() { freopen("program.in","r",stdin); freopen("program.out","w",stdout); int N;scanf("%d%d",&N,&m); for (int i=1,x,f=0;i<=N;i++) { scanf("%d",&x);if (!x) continue; if (!f) {if (x<0) f=-1; else f=1;n++;} if (x*f>0) v[n]+=x; else {if (x<0) f=-1; else f=1;v[++n]=x;} } l[0]=0;r[0]=1;l[n+1]=n;r[n+1]=n+1; for (int i=1;i<=n;i++) { l[i]=i-1;r[i]=i+1;Push(i); if (v[i]>0) now++,ans+=v[i]; } while (now>m) { int p=Heap[1],lp=l[p],rp=r[p];Pop(); if (r[lp]!=p||l[rp]!=p) continue; if ((v[p]<0&&(lp<1||n<rp))||!v[p]) continue; ans-=absl(v[p]);now--;Merge(lp,p,rp);Push(p); } return printf("%lld\n",ans),0; }
- 选一个正段
i
,将其删除,并使左右两边的负段与其合并(贪心),块数减少