版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzj1054689699/article/details/83578503
Description
有一个长度为n的序列a
你需要这个序列分成若干段,每个段可以任意指定一个数t,设v为t在这段中出现的次数,这一段的收益就是
求最大的总收益和
Solution
显而易见的是,最优情况下任何一段的开头和结尾的数都是相同的,且都是我们对这一段指定的这个数,否则不妨让他自成一段都会更优。
那么考虑DP
设
表示已经做完了前i个数,最后一段以位置i结尾的最大收益
考虑转移,假设它要从位置x转移而来,那么
其中 为1到i中a[i]出现次数
我们发现,对于相同的a,总是越往前的增长越快。
也就是说决策是具有单调性的。
我们只考虑相同的a,建立直角坐标系设横坐标为出现次数,纵坐标为贡献。
那么一个决策(x)对应在坐标系中就是一个开口向上的二次函数
对于相同的a,我们可以用一个单调栈,来维护这个东西(类比单调队列维护下凸壳)
明显越往栈顶它的增长越慢,只要更下面的决策在某一个时间点优于上面的决策,那以后一直都是更优的。
在i入栈前,考虑栈顶决策对应的曲线与栈顶下一个决策对应的曲线的交点(即栈顶下一个决策何时优于栈顶决策)和栈顶决策曲线与决策i曲线的交点
若前者时间比后者时间更靠前,说明栈顶决策没用了(要么就是决策i更优,要么就是栈顶下一个更优了),此时将栈顶弹掉。
求两个决策的交点可以采用二分。
弹到不能弹为止,将i入栈
此时还需要判断栈顶下一个决策在当前x=cnt[i]横坐标是否已经超过了栈顶,是的话将栈顶弹掉,一直弹到不能弹为止。
这时直接取栈顶转移即可
总的复杂度是
Code
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <vector>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 100005
#define LL long long
using namespace std;
LL f[N],a[N],cnt[N];
int le[10005],n,c1[10005];
vector<int>d[10005];
inline LL sqr(LL x)
{
return x*x;
}
LL get(int x,int i)
{
return f[x-1]+sqr(i-cnt[x]+1)*a[x];
}
LL fd(int x,int i)
{
int l=1,r=n+1;
while(l+1<r)
{
int mid=(l+r)>>1;
if(get(x,mid)>=get(i,mid)) r=mid;
else l=mid;
}
if(get(x,l)>=get(i,l)) return l;
else return r;
}
int main()
{
cin>>n;
fo(i,1,n)
{
scanf("%lld",&a[i]);
cnt[i]=++c1[a[i]];
while(le[a[i]]>1&&fd(d[a[i]][le[a[i]]-2],d[a[i]][le[a[i]]-1])<=fd(d[a[i]][le[a[i]]-1],i))
{
le[a[i]]--,d[a[i]].pop_back();
}
++le[a[i]];
d[a[i]].push_back(i);
int x=d[a[i]][le[a[i]]-1],y;
while(le[a[i]]>1)
{
y=d[a[i]][le[a[i]]-2];
if(get(x,cnt[i])<=get(y,cnt[i])) le[a[i]]--,d[a[i]].pop_back();
else break;
x=y;
}
f[i]=get(x,cnt[i]);
}
printf("%lld\n",f[n]);
}