前言
大约一年前,我在机房学“斜率优化”
一年后的今天,我在机房学“斜率优化”
... ...
题目
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].
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
题目大意
读入N个正整数a[1]...a[n],和一个整数k。
定义区间平均值ave(i, j)表示一个连续子区间a[i]...a[j]的元素的平均值。
求最大的区间平均值max(ave(i, j)) 1<=i<=j-k+1<=n.
分析
此乃~斜率优化板题也~
参考博客&特别鸣谢:https://www.cnblogs.com/Free-rein/archive/2012/08/09/2630190.html
解题思路
首先一定会设序列ai的部分和:Si=a1+a2+…+ai,,特别的定义S0=0。
这样可以很简洁的表示出目标函数ave(i,j)=(Sj-S(i-1))/(j-(i-1))!
如果将S函数绘在平面直角坐标系内,这就是过点Sj和点Si-1直线的斜率!
于是问题转化为:平面上已知N+1 个点,Pi(i, Si),0≤i≤N,求横向距离大
于等于F的任意两点连线的最大斜率。
构造下凸折线
有序化一下,规定对i<j,只检查Pj向Pi的连线,对Pi不检查与Pj的连线。
也就是说对任意一点,仅检查该点与在其前方的点的斜率。于是我们定义点Pi
的检查集合为Gi = {Pj, 0≤j≤i-F},特别的,当i<F时,Gi为空集。
其明确的物理意义为:在平方级算法中,若要检查ave(a, b),那么一定有Pa∈Gb;
因此平方级的算法也可以这样描述,首先依次枚举Pb点,再枚举Pa∈Gb,同时检查k(PaPb)。//k为斜率
若将Pi和Gi同时列出,则不妨称Pi为检查点,Gi中的元素都是Pi的被检查点。
当我们考察一个点Pt时,朴素的平方级算法依次选取Gt中的每一个被检查点p,
考察直线pPt的斜率。但仔细观察,若集合内存在三个点Pi, Pj, Pk,且i<j<k,三个点形成如下图
所示的的关系,即Pj点在直线PiPk的上凸部分:k(Pi, Pj)>k(Pj,Pk),就很容易可以证明Pj点是多余的。
证明:
(一)若k(Pt, Pj) > k(Pt, Pi),那么可以看出,Pt点一定要在直线PiPj的上方,即阴
影所示的1号区域。
(二)同理若k(Pt, Pj) > k(Pt, Pk),那么Pt点一定要在直线PjPk的下
方,即阴影所示的2号区域。
综合上述两种情况,若PtPj的斜率同时大于PtPi和PtPk的,Pt点一定要落在两阴影的重叠部分,
但这部分显然不满足开始时t>j 的假设。于是,Pt落在任何一个合法的位置时,PtPj的斜率要么小于PtPi,
要么小于PtPk,即不可能成为最大值,因此Pj点多余,完全可以从检查集合中删去。这个结论告诉我们,
任何一个点Pt的检查集合中,不可能存在一个对最优结果有贡献的上凸点,因此我们可以删去每一个上凸点,
剩下的则是一个下凸折线。最后需要在这个下凸折线上找一点与Pt 点构成的直线斜率最大——显然这条直
线是在与折线相切时斜率最大,如图所示。
维护下凸折线——新元素入队的处理
这一小节中,我们的目标是:用尽可能少的时间得到每一个检查点的下凸折线。
算法首先从PF开始执行:它是检查集合非空的最左边的一个点,集合内仅有一个元素P0,
而这显然满足下凸折线的要求,接着向右不停的检查新的点:PF+1,PF+2, …, PN。
检查的过程中,维护这个下凸折线:每检查一个新的点Pt,就可以向折线最右端加入一个新的点Pt-F,
同时新点的加入可能会导致折线右端的一些点变成上凸点,我们用一个类似于构造凸包的过程依次删去这些上凸点,
从而保证折线的下凸性。由于每个点仅被加入和删除一次,所以每次维护下凸折线的平摊复杂度为O(1),
即我们用O(N)的时间得到了每个检查集合的下凸折线。
最后的优化:利用图形的单调性——寻找、更新答案的处理
最后一个问题就是如何求过Pt点,且与折线相切的直线了。一种直接的方法就是二分,每次查找的复杂度是O(log2N)。
但是从图形的性质上很容易得到另一种更简便更迅速的方法:
由于折线上过每一个点切线的斜率都是一定的,而且根据下凸函数斜率的单调性,如果在检查点Pt 时找到了折线上的已知一个切点A,
那么A以前的所有点都可以删除了:过这些点的切线斜率一定小于已知最优解,不会做出更大的贡献了。
于是另外保留一个指针不回溯的向后移动以寻找切线斜率即可,平摊复杂度为为O(1)。
至此,此题算法时空复杂度均为O(N),得到了圆满的解决。
总结——斜率优化的几大步骤(蒟蒻的大白话解释)
1.分析题目,列出转移方程或要求的“目标物”的式子
2.将方程或式子写作“y=kx+b”有时是“y/x=k”的形式,即直线表达式
3.维护一个双端队列,先处理队尾,再处理队首(决策队尾、队首元素的去留)
(“先队首再队尾导致错误”的具体例子请见:https://blog.csdn.net/qq_36294918/article/details/103641669)
4.转移方程或更新答案
5.输出答案,快乐结束
特别感谢:
《浅谈数形结合思想在信息学竞赛中的应用》---周源
提供了许多关于数形结合解题的思路。
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=1e5;
double sum[MAXN+5];
int q[MAXN+5];
int n,len;
inline char __getchar()
{
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline bool qread(int &x)
{
char ch=__getchar();
if(ch==EOF)return false;
x=0;
while(!(ch>='0'&&ch<='9'))ch=__getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=__getchar();
return x;
}
void Solve()
{
memset(q,0,sizeof(q));
double Max=0;
int head=0,tail=0;
for(int i=len;i<=n;i++)
{
int now=i-len;//长度至少为k
//处理队尾
while(head<tail)
{
double k1=(sum[q[tail]]-sum[q[tail-1]])/(q[tail]-q[tail-1]);
double k2=(sum[now]-sum[q[tail]])/(now-q[tail]);
//保证下凸
if(k1>=k2)
tail--;//去掉上凸的点
else
break;
}
//入队
tail++;
q[tail]=now;
//处理队首
while(head<tail)
{
double k1=(sum[q[head]]-sum[i])/(q[head]-i);
double k2=(sum[q[head+1]]-sum[i])/(q[head+1]-i);
//去掉不优的点
if(k1<=k2)
head++;//下凸线中去掉斜率较小的点(从左往右)
else
break;
}
double k=(sum[q[head]]-sum[i])/(q[head]-i);
Max=max(Max,k);
}
printf("%.2f\n",Max);
}
int main()
{
while(qread(n)&&qread(len))
{
sum[0]=0;
for(int i=1;i<=n;i++)
{
int x;
qread(x);
sum[i]=sum[i-1]+x;
}
Solve();
}
return 0;
}
来源:CSDN
作者:顾玥_浅笑
链接:https://blog.csdn.net/qq_36294918/article/details/103641411