【雜題】[BZOJ4709]【JSOI2011】檸檬
阿新 • • 發佈:2018-11-02
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]);
}