bzoj 4709: [Jsoi2011]柠檬(分段DP+决策单调性)

4709: [Jsoi2011]柠檬

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 240   Solved: 105
[ Submit][ Status][ Discuss]

Description

Flute 很喜欢柠檬。它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬。贝壳一共有 N (1 ≤ N
 ≤ 100,000) 只,按顺序串在树枝上。为了方便,我们从左到右给贝壳编号 1..N。每只贝壳的大小不一定相同,
贝壳 i 的大小为 si(1 ≤ si ≤10,000)。变柠檬的魔法要求,Flute 每次从树枝一端取下一小段连续的贝壳,并
选择一种贝壳的大小 s0。如果 这一小段贝壳中 大小为 s0 的贝壳有 t 只,那么魔法可以把这一小段贝壳变成 s
0t^2 只柠檬。Flute 可以取任意多次贝壳,直到树枝上的贝壳被全部取完。各个小段中,Flute 选择的贝壳大小 s
0 可以不同。而最终 Flute 得到的柠檬数,就是所有小段柠檬数的总和。Flute 想知道,它最多能用这一串贝壳
变出多少柠檬。请你帮忙解决这个问题。

Input

第 1 行:一个整数,表示 N。
第 2 .. N + 1 行:每行一个整数,第 i + 1 行表示 si。

Output

仅一个整数,表示 Flute 最多能得到的柠檬数。

Sample Input

5
2 2 5 2 3

Sample Output

21


先贪心一下下

如果你选的贝壳大小为x,那么这一段的最后一个贝壳和第一个贝壳的大小必须都为x,这样才能保证最优

这样有DP方程:

dp[x] = max(dp[y-1]+s[x]*s[x]*x)

其中保证位置x上和位置y上贝壳大小都为x,s[x]为这一段大小为x贝壳的数量

假设y1<y2<=x,选择y1作为决策点比选择y2作为决策点更优,那么因为平方增幅越来越大的原因y2将再也没用了,所以可以对于每一个颜色开一个栈,每次转移检测栈顶两个决策点哪个更优,如果后面那个更优就直接弹掉栈顶

但可能出现栈顶的第一个决策点比第二个优,第二个决策点却没有第三个优,也就是说弹完之后只能保证弹掉的那个再也没用,却不能保证栈顶最优

……

还是假设栈顶的两个决策点y1和y2(y2是栈顶,y1<y2)当前点为i

这样的话每次添加元素之前,还要检测y1和y2什么时候作为决策点会比当前i点作为决策点更优,如果y1作为决策点在位置k1时优于i,y2作为决策点在位置k2时优于i,若是k1<k2,则说明y2这个决策点在超越i之前就会被y1超越,导致上面蓝字情况发生,弹出,这样就保证了栈顶每个元素超过上一个元素的时间也是单调的


#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
#define LL long long
vector<LL> st[100005];
LL n, dp[100005], a[100005], sum[100005], c[100005];
LL Calc(LL x, LL y)
{
	return dp[x-1]+(c[y]-c[x]+1)*(c[y]-c[x]+1)*a[y];
}
LL Jud(LL x, LL y)
{
	LL l, r, m, now;
	l = 1, r = n;
	now = c[y]-c[x];
	while(l<r)
	{
		m = (l+r)/2;
		if(dp[x-1]+(m+now)*(m+now)*a[x]<dp[y-1]+m*m*a[y])
			l = m+1;
		else
			r = m;
	}
	return r;
}
int main(void)
{
	LL i, j;
	scanf("%lld", &n);
	for(i=1;i<=n;i++)
		scanf("%lld", &a[i]);
	for(i=1;i<=n;i++)
	{
		c[i] = ++sum[a[i]];
		while(st[a[i]].size()>=2 && Jud(st[a[i]][st[a[i]].size()-2], i)<Jud(st[a[i]][st[a[i]].size()-1], i))
			st[a[i]].pop_back();
		st[a[i]].push_back(i);
		while(st[a[i]].size()>=2 && Calc(st[a[i]][st[a[i]].size()-2], i)>Calc(st[a[i]][st[a[i]].size()-1], i))
			st[a[i]].pop_back();
		dp[i] = Calc(st[a[i]][st[a[i]].size()-1], i);
		for(j=0;j<st[a[i]].size();j++)
			dp[i] = max(dp[i], Calc(st[a[i]][j], i));
	}
	printf("%lld\n", dp[n]);
	return 0;
}
/*
5
2 2 5 2 3
6
1 4 4 4 4 1
*/


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值