4709: [Jsoi2011]柠檬
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 252 Solved: 111
[ Submit][ Status][ Discuss]
Description
Input
Output
Sample Input
2
2
5
2
3
Sample Output
//Flute 先从左端取下 4 只贝壳,它们的大小为 2, 2, 5, 2。选择 s0 = 2,那么这一段
里有 3 只大小为 s0 的贝壳,通过魔法可以得到 2×3^2 = 18 只柠檬。再从右端取下最后一
只贝壳,通过魔法可以得到 1×3^1 = 3 只柠檬。总共可以得到 18 + 3 = 21 只柠檬。没有
比这更优的方案了。
HINT
Source
这道题有点好玩...网上很少的人写题解, 今天看了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]);
}