MAX Average Problem
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 6110 Accepted Submission(s): 1541
Problem Description
Consider a simple sequence which only contains positive integers as a1, a2 ... an, and a number k. Define ave(i,j) as the average value of the sub sequence ai ... aj, i<=j. Let’s calculate max(ave(i,j)), 1<=i<=j-k+1<=n.
Input
There multiple test cases in the input, each test case contains two lines.
The first line has two integers, N and k (k<=N<=10^5).
The second line has N integers, a1, a2 ... an. All numbers are ranged in [1, 2000].
The first line has two integers, N and k (k<=N<=10^5).
The second line has N integers, a1, a2 ... an. All numbers are ranged in [1, 2000].
Output
For every test case, output one single line contains a real number, which is mentioned in the description, accurate to 0.01.
Sample Input
10 6 6 4 2 10 3 8 5 9 4 1
Sample Output
6.50
我想这道题目应该就是斜率优化的起源,其实斜率优化并不只用于dp的优化,它是体现的是一种决策序列,将不可行解全部删除。
这道题就很明显,所有可行解组成的曲线必须是下凸曲线,单调队列是维护这种性质再好不过的数据结构。
以下全部来自周源大牛的论文:
于是问题转化为:平面上已知N+1 个点,Pi(i, Si),0≤i≤N,求横向距离大
于等于F的任意两点连线的最大斜率。
构造下凸折线
也就是说对任意一点,仅检查该点与在其前方的点的斜率。于是我们定义点Pi
的检查集合为
Gi = {Pj, 0≤j≤i-F}
因此平方级的算法也可以这样描述,首先依次枚举Pb点,再枚举Pa∈Gb,同时检查k(PaPb)。//k为斜率
考察直线pPt的斜率。但仔细观察,若集合内存在三个点Pi, Pj, Pk,且i<j<k,三个点形成如下图
所示的的关系,即Pj点在直线PiPk的上凸部分:k(Pi, Pj)>k(Pj,Pk),就很容易可以证明Pj点是多余的。
![HDU <wbr>2993-MAX <wbr>Average <wbr>Problem详解 HDU <wbr>2993-MAX <wbr>Average <wbr>Problem详解](https://i-blog.csdnimg.cn/blog_migrate/118ebe51d4169c0886e291fcb4ac0f7b.jpeg)
影所示的1号区域。同理若k(Pt, Pj) > k(Pt, Pk),那么Pt点一定要在直线PjPk的下
方,即阴影所示的2号区域。
但这部分显然不满足开始时t>j 的假设。于是,Pt落在任何一个合法的位置时,PtPj的斜率要么小于PtPi,
要么小于PtPk,即不可能成为最大值,因此Pj点多余,完全可以从检查集合中删去。这个结论告诉我们,
任何一个点Pt的检查集合中,不可能存在一个对最优结果有贡献的上凸点,因此我们可以删去每一个上凸点,
剩下的则是一个下凸折线。最后需要在这个下凸折线上找一点与Pt 点构成的直线斜率最大——显然这条直
线是在与折线相切时斜率最大,如图所示。
而这显然满足下凸折线的要求,接着向右不停的检查新的点:PF+1,PF+2, …, PN。
同时新点的加入可能会导致折线右端的一些点变成上凸点,我们用一个类似于构造凸包的过程依次删去这些上凸点,从而保证折线的下凸性。由于每个点仅被加入和删除一次,所以每次维护下凸折线的平摊复杂度为O(1),
即我们用O(N)的时间得到了每个检查集合的下凸折线。
最后的优化:利用图形的单调性
由于折线上过每一个点切线的斜率都是一定的,而且根据下凸函数斜率的单调性,如果在检查点Pt 时找到了折线上的已知一个切点A,那么A以前的所有点都可以删除了:过这些点的切线斜率一定小于已知最优解,不会做出更大的贡献了。
这里我对周大牛所说的折线的斜率那一点做一个补充。所谓折线(在这里是下凸曲线,随便一条曲线我感觉没有意义谈斜率)的某一点的斜率就是它与他的前一点连线的斜率到它与他的后一点连线的斜率,这是一个范围,延长这两条线段,会交出一块区域的,而在这块区域里的点,均会在折线上的该点与折线相切,而且可以发现随着折线的点往后挪动,这块区域在往上升,而这道题的点是(i,sum[i]),sum[i]随i增大,因此点是往上升的,因此它与折线相切的点只可能往后挪,这就是为什么可以O(1)做到的原因。
当然完全不考虑这题的特殊条件,二分查找也可以,但是此题时限卡得相当紧,二分估计过不了的。
代码:
#include<cstdio>
#include<iostream>
#define Maxn 100010
using namespace std;
int q[Maxn],sum[Maxn];
double slope(int a,int b){
return 1.0*(sum[b]-sum[a])/(b-a);
}
int main()
{
int n,k;
while(~scanf("%d%d",&n,&k)){
for(int i=1;i<=n;i++){
scanf("%d",sum+i);
sum[i]+=sum[i-1];
}
int s=0,e=0;
q[0]=0;
double res=0;
for(int i=k;i<=n;i++){
while(s<e&&slope(q[s],i)<slope(q[s+1],i)) s++;
res=res<slope(q[s],i)?slope(q[s],i):res;
while(s<e&&slope(q[e-1],q[e])>slope(q[e],i-k+1)) e--;
q[++e]=i-k+1;
}
printf("%.2f\n",res);
}
return 0;
}