生日礼物
作者:互联网
给出长度为n的序列\(\{a_i\}\),请从中选出不超过m段,最大化每段的和的和,其中\(1≤n,m≤10^5,|a_i|≤10^4\)。
解
法一:递推
这是一道区间划分的问题,容易想到设一个方程\(f[i][j]\)表示前i数字,选出j段的最大的每段的和的和,容易有
\(f[i][j]=\max(f[i-1][j],f[i-1][j-1]+a_i)\)
但是无法优化,于是萎了,参考代码就不给出了。
法二:贪心
首先可以简化问题,也就是把同符号的相邻的一段合并成一个数字,不妨记进行这个操作以后的序列为\(\{b_i\}\),长度为l,注意到它的一个性质,正负是交错的。
此时如果正数少于m,这道题目就做完了,接下来对大于m来考虑
然后根据最优性贪心,我们的选择一个极值来考虑问题,这个极值也就是绝对值的最小值,不妨设选出来的数为\(b_i\),我们可以得到以下结论
- 该数字在边界,也就是\(i=1\ or\ i=l\)
\(a_i<0\),此时显然不会有人没事去选一个负数,于是可以将它从序列中删去,删去这个操作可以利用链表来实现
\(a_i>0\),此时该数为正数中最小的数字,因为其绝对值最小,不可能单独选它,因为换成别的数字,会使结果更加优秀,如果需要将其与其他的正数合并成一段的话,又必须要选中间的负数,然而显然这个负数加上\(a_i\)绝对是小于0,于是答案中不这样操作,会让结果更加优秀。
于是我们可以得到结论,如果\(a_i\)在边界,我们应该删去它。
- 该数字在中间
\(a_i<0\) 此时容易知道,它的绝对值必然是最小的,显然不会单独去选它,而是通过它合并相邻的两个正数,如果单独选两边的正数,显然可以选择的段数会减2,而选了两边的正数再加上这一个负数,可以选择的段数会减1,而此时再随便选择一段序列中的一个正数,必然与这个负数的和都是大于等于0的,因此容易知道后者的决策绝对不会比前者差劲。
\(a_i>0\),此时容易知道它是最小的正数,单独选择它,换成别的正数结果会更加优秀,你也不会没事单独选择两边的负数,于是只能将两边的负数与之合并。
因此根据以上,我们可以得到,如果是一个绝对值最小的在中间的数字,我们就将两边的数字与之合并,然后正数的段数显然减少了1,而如果在边界,直接删去,如果删去的是正数,那么正数的段数减少了1,最终合并到正数的段数少于m,此时就可以直接选择,寻找绝对值最小利用优先队列,合并和删去利用链表,顺便优先队列中记录这个元素在链表中对应的位置,这和原问题是等价的,是一个特别难的合并性贪心,最终时间复杂度\(O(nlog(n))\)。
参考代码:
#include <iostream>
#include <cstdio>
#define il inline
#define ri register
#define ll long long
#define Size 101000
#define intmax 0x7fffffff
using namespace std;
template<class free>
struct heap{
free a[Size];int n;
il void push(free x){
a[++n]=x;ri int p(n);
while(p>1)
if(a[p]<a[p>>1])
swap(a[p],a[p>>1]),
p>>=1;
else break;
}
il void pop(){
a[1]=a[n--];ri int p(1),s(2);
while(s<=n){
if(s<n&&a[s+1]<a[s])++s;
if(a[s]<a[p])
swap(a[s],a[p]),
p=s,s<<=1;
else break;
}
}
};
template<class free>
struct list{
struct iter{
iter*pre,*next;free v;
}*head,*tail,*lt;
il void initialize(){
head=new iter(),tail=new iter();
head->next=tail,tail->pre=head;
}
il void insert(iter *p,free v){
lt=new iter{p->pre,p,v};
p->pre->next=lt,p->pre=lt;
}
il void erase(iter *p){
p->next->pre=p->pre;
p->pre->next=p->next;
p->v=intmax;
}
};
struct hl{
int d;list<int>::iter *p;
il bool operator<(const hl&x)const{
return abs(d)<abs(x.d);
}
};
heap<hl>H;
list<int>L;
list<int>::iter *p;
int a[Size];
il void read(int&);
int main(){L.initialize();
int n,m,cnt(0),ans(0);read(n),read(m);
L.insert(L.tail,0),p=L.tail->pre;
for(int i(1),a;i<=n;++i){
read(a);if((ll)p->v*a>=0)p->v+=a;
else{
if(p->v>0)++cnt;
L.insert(p->next,a);
H.push({p->v,p});
p=p->next;
}
}if(p->v>0)++cnt;H.push({p->v,p});
while(cnt>m){
while(p=H.a[1].p,p->v==intmax)H.pop();
if(p->pre==L.head||p->next==L.tail){
if(H.a[1].d>0)--cnt;
H.pop(),L.erase(p);continue;
}--cnt;
p->v+=p->pre->v+p->next->v;
L.erase(p->pre),L.erase(p->next);
H.pop(),H.push({p->v,p});
}for(int i(1);i<=H.n;++i)
if(H.a[i].p->v!=intmax&&H.a[i].d>0)
ans+=H.a[i].d;
printf("%d",ans);
return 0;
}
il void read(int &x){
x^=x;ri char c;while(c=getchar(),c==' '||c=='\n'||c=='\r');
ri bool check(false);if(c=='-')check|=true,c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
if(check)x=-x;
}
标签:pre,int,iter,next,生日礼物,il,正数 来源: https://www.cnblogs.com/a1b3c7d9/p/11257844.html