P5504 [JSOI2011] 柠檬

前置知识

贪心,dp

思路

首先我们要发现一个性质,就对于选择的一段区间一定是首尾相同同时这段区间选择的贝壳一定是首尾,不然我们不要只留有贡献的一部分一定不劣。

所以我们可以设 \(dp_i\) 表示前 \(i\) 可分成任意段的最多柠檬数量。转移比较简单从前面相同的 \(j\) 来转移.

\[dp_i=max_{j\ne i}\{dp_j+s_j(c_i-c_j+1)^2\} \]

但是有一个不好的消息是复杂度不对,是 \(O(n^2)\) 。状态显然不能优化了,所以对转移过程从相同的 \(j\) z转移需要加速。

首先我看到平方就烦,所以将平方拆掉。然后进行一些整理就可得到(转移点是 \(j\),提示 \(s_i\)\(s_j\) 可以互换)

\[dp_j+s_j(c_j-1)^2=2c_is_j(c_j-1)+dp_i-s_ic_i^2 \]

然后我们发现左边只与 \(j\) 有关,右边第一项是 \(i,j\) 混合后面几项至于 \(i\) 有关。斜率标准模型,可以直接上。

代码

#include  <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
#define ll long long
ll x[N],y[N],ans,dp[N];
int n,cnt[N],s[N],c[N],top;
vector<int> st[N]; 
double slope(int i,int j)
{
	double dx=x[j]-x[i];
	double dy=y[j]-y[i];
	return dy*1.0/(dx*1.0);
} 
int main()
{
	//ios::sync_with_stdio(0);
	//cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		cnt[s[i]]++; 
		c[i]=cnt[s[i]];
	}
	for(int i=1;i<=n;i++)
	{
		int cur=s[i],top=st[cur].size()-1;
		x[i]=1ll*s[i]*(c[i]-1);y[i]=dp[i-1]+x[i]*(c[i]-1);//炸int
		while(top>0&&slope(st[cur][top-1],st[cur][top])<=slope(st[cur][top-1],i))st[cur].pop_back(),top--; 
		st[cur].push_back(i);top++;
		while(top>0&&slope(st[cur][top-1],st[cur][top])<2*c[i])st[cur].pop_back(),top--;
		int j=st[cur][top];//cout<<top;
		dp[i]=y[j]-2*c[i]*x[j]+1ll*s[i]*c[i]*c[i];
		ans=max(ans,dp[i]); 
	}
	cout<<ans<<'\n';
	return 0;
}
posted @ 2025-05-08 18:32  exCat  阅读(12)  评论(0)    收藏  举报