HDU 2993
给一个序列
{
a
[
i
]
,
i
∈
[
1
,
n
]
}
\{a[i],i\in[1,n]\}
{a[i],i∈[1,n]},定义子区间均值为
a
v
g
(
i
,
j
)
=
s
[
j
]
−
s
[
i
−
1
]
j
−
i
+
1
avg(i,j)=\dfrac{s[j]-s[i-1]}{j-i+1}
avg(i,j)=j−i+1s[j]−s[i−1](
s
[
i
]
s[i]
s[i] 为
a
[
i
]
a[i]
a[i] 的前缀和),并且要求子区间的长度不小于
k
k
k,现在求
max
{
a
v
g
(
i
,
j
)
,
1
≤
i
≤
j
−
k
+
1
≤
n
}
\max\{avg(i,j),1\le i\le j-k+1\le n\}
max{avg(i,j),1≤i≤j−k+1≤n}。
由于
n
≤
1
0
5
n\le 10^5
n≤105 ,因此暴力是会超时的。
分析可以参考NOI2004年周源的论文
观察 a v g ( i , j ) avg(i,j) avg(i,j) 我们不难发现这是一个斜率的形式,将 i i i 作为横坐标, s [ i ] s[i] s[i] 作为纵坐标画图, a v g ( i , j ) avg(i,j) avg(i,j) 的值即为 ( i − 1 , s [ i − 1 ] ) , ( j , s [ j ] ) (i-1,s[i-1]),(j,s[j]) (i−1,s[i−1]),(j,s[j]) 这两个点连线的斜率。目标也就是找哪两个点连线的斜率最大。
依次枚举两个点的复杂度是 O ( n 2 ) O(n^2) O(n2) ,会超时。定义斜率 s i j = s [ i ] − s [ j ] i − j s_{ij}=\dfrac{s[i]-s[j]}{i-j} sij=i−js[i]−s[j],对于 k < j < i k<j<i k<j<i,假如 s i j < s j k s_{ij}<s_{jk} sij<sjk ,那么对于 i ′ i' i′ 而言, s i ′ , i > s i ′ , j s_{i',i}>s_{i',j} si′,i>si′,j ,因此 j j j 不会成为一个最优点。
所以要维护一个斜率单增的序列,当找以
i
i
i 为结尾的斜率最大值时,从单调队列的前面找。
不加上IO优化的话会超时,代码如下:
#include<iostream>
//#define WINE
#define MAXN 100010
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
int n,k,a[MAXN],h,t,q[MAXN],tot;
double s[MAXN];
double res;
const int BUF=25000000;
char Buf[BUF],*buf=Buf;
void read(int &a){
for(a=0;*buf<48;buf++);
while(*buf>47)a=a*10+*buf++-48;
}
double up(int j,int k){
return s[j]-s[k];
}
int down(int j,int k){
return j-k;
}
int main(){
#ifdef WINE
freopen("data.in","r",stdin);
#endif
tot=fread(Buf,1,BUF,stdin);
while(true){
if(buf-Buf+1>=tot)break;
read(n),read(k);
for(int i=1;i<=n;i++){
//scanf("%d",&a[i]);
read(a[i]);
s[i]=s[i-1]+a[i];
}
h=t=0;q[t++]=0;res=0;
for(int i=k;i<=n;i++){
while(h+1<t&&up(i,q[h])*down(i,q[h+1])<up(i,q[h+1])*down(i,q[h]))
h++;
res=max(res,up(i,q[h])/down(i,q[h]));
int j=i-k+1;
while(h+1<t&&up(j,q[t-1])*down(q[t-1],q[t-2])<=up(q[t-1],q[t-2])*down(j,q[t-1]))
t--;
q[t++]=j;
}
printf("%.2lf\n",res);
}
return 0;
}