[bzoj4709][柠檬]

bzoj4709

思路

首先,最优秀的分法一定是每段两端都是这一段中最多的那个,否则可以把不是的那个踢出去单独成段肯定会更优秀。然后就成了将这个序列分段,保证每段两端元素相同的最大收益和。

用a[i]记录第i个位置上的数,用s[i]记录前i个元素中a[i]出现的次数。f[i]表示以前i个数的最大收益。

首先考虑\(n^2\)的dp。明显\(f[i]=max\{f[j]+a[i]*(s[i]-s[j]+1)^2\} (a[i]==a[j])\)

    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            if(a[i]==a[j])
                f[i]=max(f[i],f[j-1]+a[i]*(s[i]-s[j]+1)*(s[i]-s[j]+1));

然后可发现,在上面的式子中,s数组是单增的,f数组也是单增的。如果知道了两个位置x和y(x<y)。通过二分,可以找到一个now使得当以后的某个位置pos的s[pos]>now之后的所有位置用x转移会比y优秀,这时y就没用了。所以用一个单调栈维护即可。

n方代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<map>
#include<queue>
using namespace std;
typedef long long ll;
const int N=100000+100;
ll read() {
    ll x=0,f=1; char c=getchar();
    while(c<'0'||c>'9') {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9') {
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}
int n;
int a[N],s[N],c[N];
ll f[N];
int main() {
    n=read();
    for(int i=1;i<=n;++i) {
        a[i]=read();
        s[i]=++c[a[i]];
    }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=i;++j)
            if(a[i]==a[j])
                f[i]=max(f[i],f[j-1]+a[i]*(s[i]-s[j]+1)*(s[i]-s[j]+1));
    cout<<f[n];
    return 0;
}

O(n)代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<map>
#include<bits/stdc++.h>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int N=100000+100;
ll read() {
    ll x=0,f=1; char c=getchar();
    while(c<'0'||c>'9') {
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9') {
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}
vector<int> sta[N];
int a[N],s[N],c[N];
ll f[N];
ll calc(int x,int y) {
    return f[x-1]+(ll)a[x]*y*y;
}
int n;
int find(int x,int y) {//寻找x比y优秀的最早时间 
    int l=1,r=n;
    int ans=n+1;
    while(l<=r) {
        int mid=l+r>>1;
        if(calc(x,mid-s[x]+1)>=calc(y,mid-s[y]+1)) ans=mid,r=mid-1;
        else l=mid+1;
    } 
    return ans;
}
int main() {
    n=read();
    for(int i=1;i<=n;++i) {
        a[i]=read();
        s[i]=++c[a[i]];
    }
    for(int i=1;i<=n;++i) {
        while(sta[a[i]].size()>=2&&find(sta[a[i]][sta[a[i]].size()-1],i)>=find(sta[a[i]][sta[a[i]].size()-2],sta[a[i]][sta[a[i]].size()-1]))
            sta[a[i]].pop_back();
        sta[a[i]].push_back(i);
        while(sta[a[i]].size()>=2&&find(sta[a[i]][sta[a[i]].size()-2],sta[a[i]][sta[a[i]].size()-1])<=s[i]) {
            sta[a[i]].pop_back();
        }
        int now=sta[a[i]].size();
        f[i]=calc(sta[a[i]][now-1],s[i]-s[sta[a[i]][now-1]]+1);
    }
    cout<<f[n];
}
全部评论

相关推荐

不愿透露姓名的神秘牛友
2025-12-17 16:48
今天九点半到公司,我跟往常一样先扫了眼电脑,屁活儿没有。寻思着没事干,就去蹲了个厕所,回来摸出手机刷了会儿。结果老板刚好路过,拍了我一下说上班别玩手机,我吓得赶紧揣兜里。也就过了四十分钟吧,我的直属领导把我叫到小隔间,上来就给我一句:“你玩手机这事儿把老板惹毛了,说白了,你可以重新找工作了,等下&nbsp;HR&nbsp;会来跟你谈。”&nbsp;我当时脑子直接宕机,一句话都没憋出来。后面&nbsp;HR&nbsp;找我谈话,直属领导也在旁边。HR&nbsp;说我这毛病不是一次两次了,属于屡教不改,不光上班玩手机,还用公司电脑看论文、弄学校的事儿。我当时人都傻了,上班摸鱼是不对,可我都是闲得发慌的时候才摸啊!而且玩手机这事儿,从来没人跟我说过后果这么严重,更没人告诉我在公司学个习也算犯错!连一次口头提醒都没有,哪儿来的屡教不改啊?更让我膈应的是,昨天部门刚开了会,说四个实习生里留一个转正,让大家好好表现。结果今天我就因为玩手机被开了。但搞笑的是,开会前直属领导就把我叫去小会议室,明明白白告诉我:“转正这事儿你就别想了,你的学历达不到我们部门要求,当初招你进来也没打算给你这个机会。”合着我没入贵厂的眼是吧?可我都已经被排除在转正名单外了,摸个鱼至于直接把我开了吗?真的太离谱了!
rush$0522:转正名单没进,大概率本来就没打算留你
摸鱼被leader发现了...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务