题目链接:
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] [3,7,9], [ 2 , 4 ] [2,4] [2,4], [ 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] [3,9], [ 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
n≤100000的数据,我们需要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)
(S−T)/(m−1)和
T
T
T的序列。而
T
−
S
/
m
=
(
T
m
−
S
)
/
m
T-S/m=(Tm-S)/m
T−S/m=(Tm−S)/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−(S−T)/(m−1)=(mT−S)/m(m−1),一定是一正一负的,所以一定存在一个序列数更少的情况,答案比原来要大。
❷如果分成了偶数个序列,总和为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;
}