1. 程式人生 > >【雜題】[BZOJ4709]【JSOI2011】檸檬

【雜題】[BZOJ4709]【JSOI2011】檸檬

Description

有一個長度為n的序列a
你需要這個序列分成若干段,每個段可以任意指定一個數t,設v為t在這段中出現的次數,這一段的收益就是 v 2 t v^2*t
求最大的總收益和
n

100000 , a i 10000 n\leq 100000,ai\leq 10000

Solution

顯而易見的是,最優情況下任何一段的開頭和結尾的數都是相同的,且都是我們對這一段指定的這個數,否則不妨讓他自成一段都會更優。

那麼考慮DP
F [ i ] F[i] 表示已經做完了前i個數,最後一段以位置i結尾的最大收益

考慮轉移,假設它要從位置x轉移而來,那麼

x i , a [ x ] = a [ i ] , F [ i ] = max ( ( c n t [ i ] c n t [ x ] + 1 ) 2 a [ i ] + F [ x 1 ] ) \forall x\leq i,a[x]=a[i],F[i]=\max\left((cnt[i]-cnt[x]+1)^2*a[i]+F[x-1]\right)

其中 c n t [ i ] cnt[i] 為1到i中a[i]出現次數

我們發現,對於相同的a,總是越往前的增長越快。

也就是說決策是具有單調性的。

我們只考慮相同的a,建立直角座標系設橫座標為出現次數,縱座標為貢獻。

那麼一個決策(x)對應在座標系中就是一個開口向上的二次函式
對於相同的a,我們可以用一個單調棧,來維護這個東西(類比單調佇列維護下凸殼)
明顯越往棧頂它的增長越慢,只要更下面的決策在某一個時間點優於上面的決策,那以後一直都是更優的。

在i入棧前,考慮棧頂決策對應的曲線與棧頂下一個決策對應的曲線的交點(即棧頂下一個決策何時優於棧頂決策)和棧頂決策曲線與決策i曲線的交點

若前者時間比後者時間更靠前,說明棧頂決策沒用了(要麼就是決策i更優,要麼就是棧頂下一個更優了),此時將棧頂彈掉。
求兩個決策的交點可以採用二分。
彈到不能彈為止,將i入棧

此時還需要判斷棧頂下一個決策在當前x=cnt[i]橫座標是否已經超過了棧頂,是的話將棧頂彈掉,一直彈到不能彈為止。

這時直接取棧頂轉移即可
總的複雜度是 O ( n log n ) O(n\log n)

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]);
}