小P的单调区间——解题报告

题目链接:

http://172.25.37.251/problem/115

题目大意:

给定一个序列,选出若干个数,将其分成若干单调的子序列(可不连续),相邻序列单调性不同,第一个序列一定为单调递增。求出所有方案中序列和的平均值的最大值。如 3 3 3 7 7 7 9 9 9 2 2 2 4 4 4 5 5 5,把它划分为 [ 3 , 7 , 9 ] [3,7,9] [379] [ 2 , 4 ] [2,4] [24] [ 5 ] [5] [5],答案为: ( 3 + 7 + 9 + 2 + 4 + 5 ) / 3 = 10 (3+7+9+2+4+5)/3=10 (3+7+9+2+4+5)/3=10。把它划分为 [ 3 , 9 ] [3,9] [39] [ 5 ] [5] [5],答案为: ( 3 + 9 + 5 ) / 2 = 8.5 (3+9+5)/2=8.5 (3+9+5)/2=8.5

题目分析:

1.我们很明显可以通过枚举选取第 i i i个点时,共分成了 j j j个序列来进行转移,那么我们可以在枚举上一个选择的数来进行转移。我们就可以愉快的暴力 D P DP DP了。如下:

//dp[i][j]表示选择第i个数后,分为j个单调序列时,数的总和的最大值
for(ll i=1;i<=n;i++)//枚举当前选择的数
{
	for(ll j=1;j<=n;j++)//枚举分成了多少个序列
	{
		for(ll k=0;k<i;k++)//枚举上一个选择的点
		{
			if(j%2==0)//当前序列要求单调递减
			{
				if(a[k]>a[i])//满足递减,可继承dp[k][j]的值,加入第j个序列中
					dp[i][j]=max(dp[i][j],max(dp[k][j],dp[k][j-1])+a[i]);
				else
					dp[i][j]=max(dp[i][j],dp[k][j-1]+a[i]);
			}
			else//当前序列要求单调递增
			{
				if(a[k]<a[i])//满足递增,可继承dp[k][j]的值,加入第j个序列中
					dp[i][j]=max(dp[i][j],max(dp[k][j],dp[k][j-1])+a[i]);
				else
					dp[i][j]=max(dp[i][j],dp[k][j-1]+a[i]);
			}
		}
		ans=max(ans,dp[i][j]/(double)j);
	}
}

2.很显然的一点是这样的做法复杂度为 O ( n 3 ) O(n^3) O(n3),是不能通过此题的,我们需要更优的方法。我们可以发现,无论能不能继承前一个 d p dp dp值时,我们都要算上将这个数单独作为一个序列时的值。因为 k < i k<i k<i,每一个 k k k都要参与计算,那么很明显我们可以利用一个前缀和来优化这个过程。如下:

for(ll j=1;j<=n;j++)//枚举序列数
{
	for(ll i=1;i<=n;i++)
	{
		f[i][j]=f[i-1][j];
		dp[i][j]=max(dp[i][j],f[i-1][j-1]+a[i]);
		for(ll k=0;k<i;k++)
		{
			if(j%2==0 && a[k]>a[i])
				dp[i][j]=max(dp[i][j],dp[k][j]+a[i]);
			else if(j%2!=0 && a[k]<a[i])
				dp[i][j]=max(dp[i][j],dp[k][j]+a[i]); 
		}
		f[i][j]=max(f[i][j],dp[i][j]);
		ans=max(ans,dp[i][j]/(double)j);
	}
}

3.虽然我们用前缀和来优化,但是我们并没有解决时间复杂度的问题。我们通过分析可以发现,枚举k的这一维,由于不具备单调性,所以不存在 O ( 1 ) O(1) O(1)的转移方式。但是我们很快可以发现,这个地方是可以利用线段树来维护的,每次查询根据j的奇偶性选择查询 [ 0 , a [ i ] ) [0,a[i]) [0,a[i])还是 ( a [ i ] , M a x ] (a[i],Max] (a[i],Max],每次将得到的 d p [ i ] [ j ] dp[i][j] dp[i][j]插入线段树。(由于数据范围的问题,这里需要离散化)。现在的时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
4.继续分析我们发现我们无法优化枚举 i i i的过程,但要通过 n ≤ 100000 n \leq 100000 n100000的数据,我们需要O(nlogn)的方法。所以我们只能优化枚举j的过程。这里要求是 O ( 1 ) O(1) O(1)的复杂度,那么说明j的数目一定是一个小的常数。下面去找到这个常数:
❶如果分成了奇数个序列,总和为 S S S,数量为 m m m,那么值为 S / m S/m S/m,我们将最后一个序列分离(一定递增),和为 T T T。我们将整个序列分为了 ( S − T ) / ( m − 1 ) (S-T)/(m-1) (ST)/(m1) T T T的序列。而 T − S / m = ( T m − S ) / m T-S/m=(Tm-S)/m TS/m=(TmS)/m S / m − ( S − T ) / ( m − 1 ) = ( m T − S ) / m ( m − 1 ) S/m-(S-T)/(m-1)=(mT-S)/m(m-1) S/m(ST)/(m1)=(mTS)/m(m1),一定是一正一负的,所以一定存在一个序列数更少的情况,答案比原来要大。
❷如果分成了偶数个序列,总和为S,数量为m,那么值为S/m,我们将最后两个序列分离
(第一个序列一定递增),和为T。我们将整个序列分为了(S-T)/(m-2)和T/2的序列。而T/2-S/m=(Tm-2S)/2m,S/m-(S-T)/(m-2)=(mT-2S)/m(m-2),一定是一正一负的,所以一定存在一个序列数更少的情况,答案比原来要大。
❸如此递归下去,我们会发现一个惊人的结论,那就是序列的个数<=2。
如此一来时间复杂度就变成了O(nlogn),解题完毕。

正解程序:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <map>
#define inf 1e15

using namespace std;
typedef long long ll;
const ll maxn=100010;
ll n,a[maxn],temp[maxn];
ll dp[maxn][4];
ll f[maxn][4];
ll tree[4*maxn];
map<ll,ll> mp;

void change(ll pos,ll l,ll r,ll pla,ll val)
{
	if(l==r)
	{
		tree[pos]=val;
		return;
	}
	ll mid=(l+r)>>1;
	if(pla<=mid)
		change(pos<<1,l,mid,pla,val);
	else
		change(pos<<1|1,mid+1,r,pla,val);
	tree[pos]=max(tree[pos<<1],tree[pos<<1|1]);
}
ll getans(ll pos,ll l,ll r,ll s,ll e)
{
	if(s<=l && r<=e)
		return tree[pos];
	ll mid=(l+r)>>1;
	ll t1=0,t2=0;
	if(s<=mid)
		t1=getans(pos<<1,l,mid,s,e);
	if(e>mid)
		t2=getans(pos<<1|1,mid+1,r,s,e);
	return max(t1,t2);
}
int main()
{
	memset(dp,0x80,sizeof(dp));
	memset(f,0x80,sizeof(f));
	scanf("%lld",&n);
	for(ll i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		temp[i]=a[i];
	}
	temp[0]=0;
	sort(temp,temp+1+n);
	ll cnt=unique(temp,temp+1+n)-temp-1;
	for(ll i=0;i<=cnt;i++)
		mp[temp[i]]=i+1;
	dp[0][1]=0;
	f[0][1]=0;
	double ans=-inf;
	cnt++;
	for(ll j=1;j<=2;j++)
	{
		memset(tree,0x80,sizeof(tree));
		change(1,1,cnt,1,0);
		for(ll i=1;i<=n;i++)
		{
			f[i][j]=f[i-1][j];
			dp[i][j]=max(dp[i][j],f[i-1][j-1]+a[i]);
			if(j%2==1)
			{
				ll value=getans(1,1,cnt,1,mp[a[i]]-1);
				dp[i][j]=max(dp[i][j],value+a[i]);
			}
			else
			{
				ll value=getans(1,1,cnt,mp[a[i]]+1,cnt);
				dp[i][j]=max(dp[i][j],value+a[i]);
			}
			change(1,1,cnt,mp[a[i]],dp[i][j]);
			f[i][j]=max(f[i][j],dp[i][j]);
			ans=max(ans,dp[i][j]/(double)j);
		}
	}
	printf("%.3lf",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值