NOI.AC#2139选择【斜率优化dp,树状数组】

正题

题目链接:http://noi.ac/problem/2139


题目大意

给出\(n\)个数字的序列\(a_i\)。然后选出一个不降子序列最大化子序列的\(a_i\)和减去没有任何一个数被选中的区间数量。

\(1\leq n\leq 10^6,1\leq a_i\leq 10^8\)


解题思路

嗯,考虑朴素的\(dp\)方程,设\(f_i\)表示以\(i\)为末尾的值就有

\[f_i=f_j+a_i+\frac{(i-j-1)(i-j)}{2} \]

然后展开整理一下都乘二就是

\[f_i=f_j+2a_i+i^2-i+j^2+j-2ij(a_j\leq a_i,j<i) \]

除了\(a_j\leq a_i\)就是一个标准的斜率优化式子了

然后这个东西其实挺好搞的,因为多一个限制直接上\(CDQ\)就好了,但是每次左边要归并排序,这样时间复杂度就是\(O(n\log n)\)的了

但其实还有更暴力的做法,因为既然一个\(CDQ\)能做到,那么找些数据结构之类的也肯定能做到。

对于树状数组上每个节点维护一个凸壳然后暴力查询就好了

时间复杂度\(O(n\log n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define ll long long
#define lowbit(x) (x&-x)
using namespace std;
const ll N=1e6+10;
ll n,m,a[N],b[N],l[N],r[N],f[N],k[N];
vector<ll >q[N];
ll calc(ll i,ll j)
{return k[i]+2*i*j;}
void Change(ll x,ll i){
	while(x<=m){
		while(l[x]<r[x]&&(k[i]-k[q[x][r[x]]])*(q[x][r[x]]-q[x][r[x]-1])>=(k[q[x][r[x]]]-k[q[x][r[x]-1]])*(i-q[x][r[x]]))
			r[x]--,q[x].pop_back();
		q[x].push_back(i);r[x]++;x+=lowbit(x);
	}
	return;
}
ll Ask(ll x,ll i){
	ll ans=-1e18;
	while(x){
		while(l[x]<r[x]&&calc(q[x][l[x]],i)<calc(q[x][l[x]+1],i))
			l[x]++;
		if(l[x]<=r[x])ans=max(ans,calc(q[x][l[x]],i));
		x-=lowbit(x);
	}
	return ans;
}
signed main()
{
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&a[i]),b[i]=a[i];
	sort(b+1,b+1+n);
	m=unique(b+1,b+1+n)-b-1;
	for(ll i=1;i<=m;i++)r[i]=-1;
	Change(1,0);
	for(ll i=1;i<=n;i++){
		ll x=lower_bound(b+1,b+1+m,a[i])-b;
		f[i]=Ask(x,i)+2*a[i]-i*i+i;
		k[i]=f[i]-i*i-i;
		Change(x,i);
	}
	ll ans=-1e18;
	for(ll i=1;i<=n;i++)
		ans=max(ans,f[i]/2-(n-i+1)*(n-i)/2);
	printf("%lld\n",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/14598699.html