【BZOJ4709】【Jsoi2011】柠檬

Description

  
  传送门
  
  题意简述:将序列划分成任意多段,从每一段选出一个数\(x\),获得\(在这一段出现的次数x*(x在这一段出现的次数)\)的贡献。求总贡献最大值。
  
  
  
  
  

Solution

  
​  首先,要发现一个很重要的性质:如果某一段选了\(x\),那么这一段一定是以\(x\)开头、以\(x\)结尾的一段。否则,可以将此段缩减至以\(x\)开头、以\(x\)结尾的更小的一段,虽然贡献没有变,但留给其他段的机会更多。
  
  设\(f_i\)表示\(1...i\)的贡献最大值。记\(a_i\)表示\(i\)的数值,\(b_i\)表示\(a_i\)在相同的值中是第几个出现的。显然如果要从别的\(f_j\)转移到\(f_i\),必须满足\(a_{j+1}==a_i\)。我们有转移方程:

\[f_i=\max \{f_{j-1}+a_i(b_i-b_j+1)^2\}\;\;\;j\le i,a_j=a_i \]

  设\(j\)为最优转移点:

\[\begin{aligned} f_i&=f_{j-1}+a_i(b_i-(b_j-1))^2\\ f_i&=f_{j-1}+a_i(b_i^2-2b_i(b_j-1)+(b_j-1)^2)\\ f_i&=f_{j-1}+a_ib_i^2-2a_ib_i(b_j-1)+a_i(b_j-1)^2\\ f_{j-1}+a_i(b_j-1)^2&=2a_ib_i(b_j-1)+f_i-a_ib_i^2 \end{aligned} \]

  这其实是一个直线的式子:\(k=2a_ib_i\)\(x=(b_j-1)\)\(b=(f_i-a_ib_i^2)\)\(y=f_{j-1}+a_i(b_j-1)^2\).
  
  其中\(a_i\)看似和\(i\)有关,无法继续推理。但由于转移的\(j\)满足\(a_j=a_i\),所以每一个位置的数在参与上述DP时,相关联的\(a\)其实就是每一个元素自己的数值,是一个定值。
  
  把每一个元素看成二维平面的一个点\((x,y)\)。由于最优转移相当于最大化截距,那么最优转移点\(j\)可以看做在斜率为\(k\)的时候上凸包碰到的第一个点。
  
  那么我们扫描序列时,维护每一个数值对应的上凸包,每次查询时在上面二分即可。
  
  时间复杂度\(\mathcal O(n \lg n)\)
  
  当然,也可以用斜率优化直接做。
  
  
  

Code

  

#include <cstdio>
#include <vector>
#define k(i) (2LL*a[i]*b[i])
#define x(i) (b[i]-1LL)
#define y(i) (f[i-1]+1LL*a[i]*(b[i]-1)*(b[i]-1))
#define b(i) (f[i]-1LL*a[i]*b[i]*b[i])
#define pb push_back
#define db pop_back
using namespace std;
typedef long long ll;
const int N=100005,S=10005;
const double EPS=1e-6;
int n,a[N],ecnt[S],b[N];
ll f[N];
vector<int> s[S];
int slen[S];
double slope(int u,int v){return 1.0*(y(v)-y(u))/(x(v)-x(u));}
int query(int col,int k){
	k=2*col*k;
	int l=0,r=slen[col]-2,mid;
	while(l<=r){
		mid=(l+r)>>1;
		if(slope(s[col][mid],s[col][mid+1])-EPS<=k) r=mid-1;
		else l=mid+1;
	}
	return s[col][l];
}
void insert(int col,int i){
	int sz=slen[col];
	while(sz>1&&slope(s[col][sz-2],s[col][sz-1])<slope(s[col][sz-1],i)) 
		sz--,slen[col]--,s[col].db();
	s[col].pb(i);
	slen[col]++;
}
int main(){
	scanf("%d",&n); 
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
		b[i]=++ecnt[a[i]];
	}
	for(int i=1;i<=n;i++){
		insert(a[i],i);
		int j=query(a[i],b[i]);
		f[i]=(j?f[j-1]:f[i-1])+1LL*a[i]*(b[i]-b[j]+1)*(b[i]-b[j]+1);
	}
	printf("%lld\n",f[n]);
	return 0;
}
posted @ 2018-07-01 08:23  RogerDTZ  阅读(130)  评论(0编辑  收藏  举报