【BZOJ4709】柠檬(动态规划,单调栈)

【BZOJ4709】柠檬(动态规划,单调栈)

题面

BZOJ

题解

从左取和从右取没有区别,本质上就是要分段。
\(f[i]\)表示前\(i\)个位置的最大值。
那么相当于我们枚举一个前面的位置\(j\),然后找到这一段中最大的\(s_0t^2\)
但是这样子很不优秀。
我们贪心的思考一下,既然这一段最后加起来只能变成某一个\(s_0\)
那么,我们这一段开头和结尾都一定要是\(s_0\)
否则我们把结尾那些不等于\(s_0\)的单独分开一段,
这样子的答案一定不会更差,前缀同理。
因此每次的\(s_0\)一定由前面的某个\(s_0\)转移过来。
转移是\(f[i]=f[j-1]+s[i]t^2\),其中\(t^2\)\([j,i]\)\(s[i]\)的个数。
发现\(t^2\)增长很快于\(y=x\),显然这个式子是具有决策单调性的。
如果当前位置\(k\lt j\),那么一旦\(k\)的转移优于了\(j\),那么\(k\)就永远优于\(j\)了。(这不显然吗?
那么,对于每一个\(s\)都维护一个单调栈(\(vector\))
每次将后面不优的全部弹出去,然后进行转移。
注意几点:
首先是不优的靠计算,记录一下当前位置的前缀\(t\)的值,然后每次二分检查单调栈里面的第二个元素是否优于栈顶元素,也就是二分查找一下超过的时间。
还有一种可能出现的情况,即当前第二个元素不比栈顶优秀,但是第三个元素比栈顶优秀。
对于栈顶的几个元素,假设\(a<b<c\),如果\(a\)超过\(b\)的时间要早于\(b\)超过\(c\)的时间,那么\(b\)是没有意义的。
所以对于当前位置\(i\),我们检查栈顶元素和第二个元素超过\(i\)的时间
如果第二个元素超过\(i\)的时间更早,那么第一个元素就没有意义了,可以直接弹掉。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 111111
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
ll f[MAX];
vector<int> g[MAX];
int n,a[MAX],num[MAX],s[MAX];
ll calc(int j,int x){return f[j-1]+1ll*a[j]*x*x;}
int Time(int x,int y)
{
	int l=1,r=n,ret=n+1;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(calc(x,mid-s[x]+1)>=calc(y,mid-s[y]+1))ret=mid,r=mid-1;
		else l=mid+1;
	}
	return ret;
}
int main()
{
	n=read();
	for(int i=1;i<=n;++i)
	{
		s[i]=++num[a[i]=read()];int l=g[a[i]].size();
		while(l>=2&&Time(g[a[i]][l-2],g[a[i]][l-1])<=Time(g[a[i]][l-1],i))
			--l,g[a[i]].pop_back();
		g[a[i]].push_back(i);++l;
		while(l>=2&&Time(g[a[i]][l-2],g[a[i]][l-1])<=s[i])
			--l,g[a[i]].pop_back();
		f[i]=calc(g[a[i]][l-1],s[i]-s[g[a[i]][l-1]]+1);
	}
	printf("%lld\n",f[n]);
}

posted @ 2018-07-13 20:38  小蒟蒻yyb  阅读(445)  评论(0编辑  收藏  举报