其他分享
首页 > 其他分享> > 生日礼物

生日礼物

作者:互联网

生日礼物

给出长度为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\),我们可以得到以下结论

  1. \(a_i<0\),此时显然不会有人没事去选一个负数,于是可以将它从序列中删去,删去这个操作可以利用链表来实现

  2. \(a_i>0\),此时该数为正数中最小的数字,因为其绝对值最小,不可能单独选它,因为换成别的数字,会使结果更加优秀,如果需要将其与其他的正数合并成一段的话,又必须要选中间的负数,然而显然这个负数加上\(a_i\)绝对是小于0,于是答案中不这样操作,会让结果更加优秀。

于是我们可以得到结论,如果\(a_i\)在边界,我们应该删去它。

  1. \(a_i<0\) 此时容易知道,它的绝对值必然是最小的,显然不会单独去选它,而是通过它合并相邻的两个正数,如果单独选两边的正数,显然可以选择的段数会减2,而选了两边的正数再加上这一个负数,可以选择的段数会减1,而此时再随便选择一段序列中的一个正数,必然与这个负数的和都是大于等于0的,因此容易知道后者的决策绝对不会比前者差劲。

  2. \(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