LuoguP5504 [JSOI2011]柠檬

LuoguP5504 [JSOI2011]柠檬

题目描述

Solution

容易发现一个性质:每一段划分区间的首尾两个元素相同。
因为倘若不相同的话其中至少一个元素也就不产生贡献,将其划分在其他区间一定不会变劣。

因此就可以写出一个简单的 O ( n 2 ) O(n^2) O(n2) d p dp dp
f i = f j − 1 + ( s i − s j + 1 ) 2                ( a i = a j ) f_i=f_{j-1}+(s_i-s_j+1)^2\;\;\;\;\;\;\;(a_i=a_j) fi=fj1+(sisj+1)2(ai=aj)
其中 s i s_i si表示在 i i i之前的与 i i i相同的元素的个数。

考虑决策单调性:
设有 j 1 < j 2 < i j_1<j_2<i j1<j2<i a j 1 = a j 2 a_{j_1}=a_{j_2} aj1=aj2,令 g e t a n s ( x , y ) getans(x,y) getans(x,y)表示 f x − 1 + ( s y − s x + 1 ) 2 f_{x-1}+(s_y-s_x+1)^2 fx1+(sysx+1)2

g e t a n s ( j 1 , i ) ≥ g e t a n s ( j 2 , i ) getans(j_1,i)\geq getans(j_2,i) getans(j1,i)getans(j2,i),则对于所有 i ′ ≥ i i'\geq i ii,都有 g e t a n s ( j 1 , i ′ ) ≥ g e t a n s ( j 2 , i ′ ) getans(j_1,i')\geq getans(j_2,i') getans(j1,i)getans(j2,i),因为两者的增长都是平方级别的。

因此我们可以用单调栈维护这一过程。
对于每一种 a i a_i ai开一个单调栈,我们希望的是保证栈中元素的 g e t a n s ( . . . , i ) getans(...,i) getans(...,i)始终递增。每次如果发现栈顶元素没有它下面一个元素优,就弹栈。

但这并不是完全正确的,随着 i i i的后移,因为前面的元素增长快,所以可能存在一个 j 1 < j 2 < j 3 < i j_1<j_2<j_3<i j1<j2<j3<i,使得 g e t a n s ( j 1 , i ) > g e t a n s ( j 3 , i ) getans(j_1,i)>getans(j_3,i) getans(j1,i)>getans(j3,i),但 g e t a n s ( j 2 , i ) < g e t a n s ( j 3 , i ) getans(j_2,i)<getans(j_3,i) getans(j2,i)<getans(j3,i)

因此我们还需要保证栈中每一个元素 x x x超过上面一个元素 y y y的时间小于 y y y超过它上面的元素 z z z的时间。

这个某个元素超过另一个元素的时间可以二分求得。

所以我们维护单调栈时额外添加一个判断 g e t a n s ( s t a c k [ t o p − 2 ] ) > g e t a n s ( s t a c k [ t o p − 1 ] ) getans(stack[top-2])>getans(stack[top-1]) getans(stack[top2])>getans(stack[top1])的时间是否比 g e t a n s ( s t a c k [ t o p − 1 ] ) > g e t a n s ( s t a c k [ t o p ] ) getans(stack[top-1])>getans(stack[top]) getans(stack[top1])>getans(stack[top])的时间短即可。

时间复杂度 O ( n l g n ) O(nlgn) O(nlgn)

#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <string.h>
//#include <unordered_set>
//#include <unordered_map>
//#include <bits/stdc++.h>

#define MP(A,B) make_pair(A,B)
#define PB(A) push_back(A)
#define SIZE(A) ((int)A.size())
#define LEN(A) ((int)A.length())
#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define fi first
#define se second

using namespace std;

template<typename T>inline bool upmin(T &x,T y) { return y<x?x=y,1:0; }
template<typename T>inline bool upmax(T &x,T y) { return x<y?x=y,1:0; }

typedef long long ll;
typedef unsigned long long ull;
typedef long double lod;
typedef pair<int,int> PR;
typedef vector<int> VI;

const lod eps=1e-11;
const lod pi=acos(-1);
const int oo=1<<30;
const ll loo=1ll<<62;
const int mods=998244353;
const int MAXN=600005;
const int INF=0x3f3f3f3f;//1061109567
/*--------------------------------------------------------------------*/
inline int read()
{
	int f=1,x=0; char c=getchar();
	while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }
	while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+(c^48); c=getchar(); }
	return x*f;
}
ll f[MAXN];
vector<int> V[MAXN];
int a[MAXN],s[MAXN],cnt[MAXN],n;
ll Getans(int x,int y) { return f[x-1]+1ll*a[x]*y*y; }
ll getans(int x,int y) { return f[x-1]+1ll*a[x]*(s[y]-s[x]+1)*(s[y]-s[x]+1); }
int check(int x,int y)
{
	int l=s[y],r=n+1;
	while (l<r)
	{
		int mid=(l+r)>>1;
		if (Getans(x,mid-s[x]+1)>=Getans(y,mid-s[y]+1)) r=mid;
		else l=mid+1;
	}
	return r;
}
int main()
{
	n=read();
	for (int i=1;i<=n;i++) s[i]=++cnt[a[i]=read()];
	f[0]=0;
	for (int i=1,sz;i<=n;i++)
	{
		sz=V[a[i]].size()-1;
		while (sz>=1&&check(V[a[i]][sz-1],V[a[i]][sz])<=check(V[a[i]][sz],i)) V[a[i]].pop_back(),sz--;
		V[a[i]].PB(i),sz++;
		while (sz>=1&&getans(V[a[i]][sz-1],i)>=getans(V[a[i]][sz],i)) V[a[i]].pop_back(),sz--;
		f[i]=getans(V[a[i]][sz],i);
	}
	printf("%lld\n",f[n]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值