[BZOJ]4709 柠檬 DP单调性优化 + 单调栈

4709: [Jsoi2011]柠檬

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 252   Solved: 111
[ 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
//Flute 先从左端取下 4 只贝壳,它们的大小为 2, 2, 5, 2。选择 s0 = 2,那么这一段
里有 3 只大小为 s0 的贝壳,通过魔法可以得到 2×3^2 = 18 只柠檬。再从右端取下最后一
只贝壳,通过魔法可以得到 1×3^1 = 3 只柠檬。总共可以得到 18 + 3 = 21 只柠檬。没有
比这更优的方案了。

HINT

Source

[ Submit][ Status][ Discuss]


HOME Back

  

  这道题有点好玩...网上很少的人写题解, 今天看了neither_nor的题解之后对于某个地方还是很困惑,不过多多证明后还是豁然开朗. 下面写一下自己的理解.

  先附上neither_nor的题解.

  考虑DP,f[i]表示前i个的最大价值.

  那么在最后一段所选的颜色一定是i的情况下,最后一段的开头的颜色也一定是i的颜色,否则开头一段也没有贡献,不然单分出去一段.

  我们发现对于任意一个i,他所选的最后一段所指定的颜色一定是i的颜色,因为否则的话i这个点就会没有贡献,一定不如最后一段只选一个i.

  那么对于f[i],假设之前存在一个点j,这个点的颜色a[j]与i的颜色a[i]相等,那么我们可以让f[i]由j转移,价值为f[j-1]+a[j]*(s[i]-s[j]+1)^2,s[i]代表前i个数里a[i]出现的次数

  我们发现对于任意一个颜色x,随着这个颜色的数不断出现,由每一个之前的a[j]=x的j转移所产生的价值都会不断增加,且j越小,增加的越块,如果有j1<j2,并且j1当前的价值比j2大了,那么j2就没有用了,于是我们可以维护一个单调栈,每次如果栈顶第二个的元素超过了栈顶,就把栈顶弹出,决策的时候直接用栈顶决策

  而我们发现可能会出现栈顶第三个元素超过了栈顶,而栈顶第二个元素还没超过栈顶的情况,我们需要避免这种情况。我们发现对于任意的j1<j2<i1<i2,如果j1超过i1的时间比j2超过i1的时间要早,那么j1超过i2的时间也一定比j2超过i2的时间早,证明显然,并且我们对于任意的(j1,j2),j1<j2,我们都能直接算出在第几个颜色与j1,j2相等的数出现的时候,j1会超过j2,那么在我们即将把i压入栈顶的时候,只要当前栈顶第二个元素超过i的时间比当前栈顶超过i的时间要早,那么栈顶就也没用了,我们就可以弹栈,这样的话栈里的每一个元素超过上一个元素的时间也是单调的,于是就没有问题了.

  我对最后一段的理解是:

  因为我们要避免每一个元素超过上一个元素的时间也是单调的, 那么设栈里第二个元素为j2, 栈顶即第一个元素为j1, 即将加入栈的为i, 那么当j2超过j1的时候比j1超过i的时候早,那么我们弹栈. 先考虑为什么这么做, 首先这样能弹栈说明了j1一定不优. 证明: 由于本身i这个地方可以自成一段,也就是说i是可以用i点这个决策的, 那么当j1比i优的时候, j1不应该弹栈,否则肯定弹栈.

  但又因为j1如果比i优的话, 当j1比i优的时候, j2已经比j1优了(这是弹栈条件), 所以j1还是要被弹栈. 所以满足这个条件弹栈肯定是没有问题的. 接下来我们再看为什么这样就能保证每一个元素超过上一个元素的时间也是单调的,因为我们通过弹栈保证了, j2比j1优的时间一定 比 j1比i优的时间晚(否则j1就被弹出去了), 那么因为我们一直这样保证, 所以当i已经不再是栈顶元素是, 因为j2比j1优的时间一定比j1比i优的时间晚, 那么设压在i之上的元素是p, 我们也保证了j1比i优的时间晚肯定比 i比p优的时间晚(否则我们要弹栈, 现在讨论的是一个合法的即真正单调的单调栈). 这样递推下去,栈中一层比一层晚, 所以就不会出现栈顶第三个元素超过了栈顶,而栈顶第二个元素还没超过栈顶的情况.

#include<stdio.h>
#include<vector>
using namespace std;
typedef long long dnt;
const int maxn = 100005;
int n;
dnt f[maxn];
vector<int> g[10005];
int s[maxn], a[maxn], cnt[10005];
inline dnt calc(int x, int y){
	return f[x - 1] + (dnt) a[x] * y * y;
}
inline int find(int x, int y){
	int lf = 1, rg = n;
	int ans = n + 1;
	while(lf <= rg){
		int mid = (lf + rg) >> 1;
		if(calc(x, mid - s[x] + 1) >= calc(y, mid - s[y] + 1)) ans = mid, rg = mid - 1;
		else lf = mid + 1;
	}
	return ans;
}
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; ++i){
		int x;
		scanf("%d", &x);
		a[i] = x, s[i] = ++cnt[x];
		while(g[x].size() >= 2 && find(g[x][g[x].size() - 2], g[x][g[x].size() - 1])
							   <= find(g[x][g[x].size() - 1], i)) g[x].pop_back();
		g[x].push_back(i);
		while(g[x].size() >= 2 && find(g[x][g[x].size() - 2], g[x][g[x].size() - 1]) <= s[i])
							   g[x].pop_back();
		f[i] = calc(g[x][g[x].size() - 1], s[i] - s[g[x][g[x].size() - 1]] + 1);
	}
	printf("%lld\n", f[n]);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值