【洛谷5504】[JSOI2011] 柠檬(决策单调性优化DP)

点此看题面

  • 有一个长度为(n)的序列,你可以将序列划分成若干段。
  • 对于每段,指定一个数(x),获得贡献为(x imes c(x)^2)(c(x))为这一段中(x)的个数)。
  • 求最大的总贡献。
  • (nle10^5)

分元素讨论

首先,显然我们令划出的每个区间左右端点元素相同一定是最优的,且此时我们必然选择当前端点上的元素作为这个区间的指定元素。

证明的话就是如果我们选择不在端点上的元素作为指定元素,那么完全可以让两端直至第一个指定元素之前的所有元素自成若干区间,对于这个区间答案不变,而新建区间后又能获得更多贡献,一定会更优。

所以说,我们只需要考虑同种元素之间的转移即可。

决策单调性

考虑转移方程:((a_i=a_j)(w)表示当前位置是(a_i)第几次出现)

[f_i=f_j+a_i(w_i-w_j+1)^2 ]

显然,(a_i(w_i-w_j+1)^2)作为一个二次函数一定递增,且斜率越来越大。

所以我们发现,前面的位置一定会在某一时刻超过后面的位置。

于是我们维护一个单调栈,每次往栈中加入一个元素(i)时,二分出栈顶元素优于当前元素的时刻(k_i)

如果(k_i)早于当前时刻,那么(i)已经被超越了,无须入栈

如果(k_i)早于栈顶元素优于前一元素的时刻,那么栈顶元素就废了,直接弹出。接着不断重复此过程,直至(k_i)晚于栈顶元素优于前一元素的时刻,则令(i)入栈。

发现一个位置是可能从自己转移的,且(i)的贡献和(f_i)无关,所以我们先入栈再转移

这样一来这道题就做完了。

似乎用斜率优化反而不带log更快?

代码:(O(nlogn))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,w[N+5],c[N+5],k[N+5];LL f[N+5];stack<int> S[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
#define Calc(x,i,p) (f[i-1]+1LL*x*(p-w[i]+1)*(p-w[i]+1))//元素为x,从i位置转移到x第p次出现时的贡献
I int Find(CI x,CI a,CI b)//二分超越时刻
{
	RI l=w[b]-1,r=n,mid;W(l^r) mid=l+r+1>>1,Calc(x,a,mid)<Calc(x,b,mid)?l=mid:r=mid-1;return l;
}
int main()
{
	RI i,x,t;LL ans=0;for(F.read(n),i=1;i<=n;++i)//决策单调性优化DP
	{
		F.read(x),w[i]=++c[x],k[i]=1e9;//w记录当前元素是第几次出现
		W(!S[x].empty()&&k[S[x].top()]<w[i]) S[x].pop();//弹出已经被超越的元素
		W(!S[x].empty()&&k[S[x].top()]<(k[i]=Find(x,S[x].top(),i))) S[x].pop();//弹出无用的栈顶元素
		k[i]>=w[i]&&(S[x].push(i),0),f[i]=max(f[i],Calc(x,S[x].top(),w[i]));//如果有用才入栈,然后从栈顶转移
	}return printf("%lld
",f[n]),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5504.html