test 9 小p的单调数列 (dp+数学证明)



题解:dp+数学证明

先分析一下这个单调子序列的性质。一定存在一个最优子序列,他的单调区间的长度不超过2.

假设最优子序列有m个单调区间,序列的数字总数为s,那么最大艺术价值为s/m

设最后一个区间的数字总数为t,那么s/m的范围一定是在s-t/m-1,t之间的。为什么呢?因为是区间求平均值,所以我们只保留两个值中较大的即可。那么我们总能找出区间个数为1或2的合法最优子序列。

那么选出来的子序列就有两种可能。

一种是一个单增序列,直接求值最大的单调序列就可以了;

另一种是单增序列+单减序列,正反求然后接起来。

然后用线段树优化一下。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 100003
using namespace std;
int n,m,c[N],pos[N],cnt;
double g[N],f[N],a[N],ans,tr[N*4];
int cmp(int x,int y)
{
	return a[x]<a[y];
}
void update(int now)
{
	tr[now]=max(tr[now<<1],tr[now<<1|1]);
}
void change(int now,int l,int r,int x,double v)
{
	if (l==r) {
		tr[now]=max(tr[now],v); 
		return;
	}
	int mid=(l+r)/2;
	if (x<=mid) change(now<<1,l,mid,x,v);
	else change(now<<1|1,mid+1,r,x,v);
	update(now);
}
double qjask(int now,int l,int r,int ll,int rr)
{
	if (ll>rr) return 0;
	if (ll<=l&&r<=rr) return tr[now];
	int mid=(l+r)/2;
	double ans=0;
	if (ll<=mid) ans=max(ans,qjask(now<<1,l,mid,ll,rr));
	if (rr>mid) ans=max(ans,qjask(now<<1|1,mid+1,r,ll,rr));
	return ans;
}
int main()
{
	freopen("seq.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;i++) scanf("%lf",&a[i]),c[i]=i;
	sort(c+1,c+n+1,cmp);
	pos[c[1]]=++cnt;
	for (int i=2;i<=n;i++)
	 if (a[c[i]]!=a[c[i-1]])  pos[c[i]]=++cnt;
	 else pos[c[i]]=cnt;
	f[1]=a[1]; change(1,1,cnt,pos[1],f[1]);
	g[n]=a[n];
	for (int i=2;i<=n;i++)
	{
	  f[i]=qjask(1,1,cnt,1,pos[i]-1)+a[i];
	  change(1,1,cnt,pos[i],f[i]);
    }
	memset(tr,0,sizeof(tr));
	change(1,1,cnt,pos[n],g[n]);
    for (int i=n-1;i>=1;i--)
    {
      g[i]=qjask(1,1,cnt,1,pos[i]-1)+a[i];
      change(1,1,cnt,pos[i],g[i]);
    }
    for (int i=1;i<=n;i++)
     {
     	ans=max(ans,(g[i]+f[i]-a[i])/2);
     	ans=max(ans,f[i]);
     }
    printf("%.3lf",ans);
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值