题意:
给定一个长度为
n
n
n的序列和三个正整数
k
,
L
,
R
k,L,R
k,L,R,要求找出一个长度在
[
L
,
R
]
[L,R]
[L,R]之间的区间
[
i
,
j
]
[i,j]
[i,j],最大化
m
a
x
{
a
[
i
.
.
.
j
]
}
−
m
i
n
{
a
[
i
.
.
.
j
]
}
j
−
i
+
k
\frac{max\{a[i...j]\}-min\{a[i...j]\}}{j-i+k}
j−i+kmax{a[i...j]}−min{a[i...j]},求这个最大值。
n
<
=
50000
n<=50000
n<=50000
题解:
求一个分数的最大值经常是要用到分数规划的。我们先来考虑如果区间选出的最大值和最小值之间距离小于等于
L
L
L,那么我们肯定想在分子一定的时候分母尽可能小,于是我们要让分母中的
j
−
i
=
L
j-i=L
j−i=L,于是我们先算一遍如果要区间长度只能是
L
L
L的情况的答案。
这种情况的答案其实不难算,算的方法就是枚举一个区间的左端点,然后查询从这个左端点开始的长度为 L L L的区间的最大值和最小值来更新答案,最大值最小值可以用st表或者线段树维护一下就行了。
接下来我们考虑长度在 ( L , R ] (L,R] (L,R]的区间对答案的贡献。我们基于刚才的贪心的思想,我们会让分子一定的时候分母尽可能的小,于是长度在 ( L , R ] (L,R] (L,R]之间的情况一定是最大值和最小值的左右端点,这样才能保证分母尽可能的小。
我们考虑分数规划,设 m i d mid mid为当前二分值,转化成 m a x { a [ i . . . j ] } − m i n { a [ i . . . j ] } > = m i d ∗ ( j − i + k ) max\{a[i...j]\}-min\{a[i...j]\}>=mid*(j-i+k) max{a[i...j]}−min{a[i...j]}>=mid∗(j−i+k) 化一下式子 max i = l r a [ i ] − min i = 1 r a [ i ] > = m i d ∗ ( j − i + k ) \max_{i=l}^ra[i]-\min_{i=1}^ra[i]>=mid*(j-i+k) i=lmaxra[i]−i=1minra[i]>=mid∗(j−i+k) 我们可以转化为 max 1 < = i , j < = n , L < j − i + 1 < = R { a [ i ] + m i d ∗ i − ( a [ j ] + m i d ∗ j ) } > = m i d ∗ k \max_{1<=i,j<=n,L<j-i+1<=R}\{a[i]+mid*i-(a[j]+mid*j)\}>=mid*k 1<=i,j<=n,L<j−i+1<=Rmax{a[i]+mid∗i−(a[j]+mid∗j)}>=mid∗k 我们设 a [ i ] + m i d ∗ i = b [ i ] a[i]+mid*i=b[i] a[i]+mid∗i=b[i],那么就是 max 1 < = i , j < = n , L < j − i + 1 < = R { b [ i ] − b [ j ] } > = m i d ∗ k \max_{1<=i,j<=n,L<j-i+1<=R}\{b[i]-b[j]\}>=mid*k 1<=i,j<=n,L<j−i+1<=Rmax{b[i]−b[j]}>=mid∗k 我们发现这个式子是可以用单调队列维护 b [ i ] b[i] b[i]的,这样我们就可以枚举一个最大值,单调队列维护一个最小值,然后正着倒着扫两遍,再与固定长度为 L L L的但取个 m a x max max,就可以求出最终的答案了。
然而好像这个线段树可以被单调队列替换掉,好像没什么存在的意义了QAQ
代码:
#include <bits/stdc++.h>
using namespace std;
int T,n,k,L,R,a[50010],q[500010];
double ans,b[50010];
const double eps=1e-8;
struct node
{
int l,r,mn,mx;
}tr[500010];
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline void build(int rt,int l,int r)
{
tr[rt].l=l;
tr[rt].r=r;
if(l==r)
{
tr[rt].mx=a[l];
tr[rt].mn=a[l];
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
tr[rt].mn=min(tr[rt<<1].mn,tr[rt<<1|1].mn);
tr[rt].mx=max(tr[rt<<1].mx,tr[rt<<1|1].mx);
}
inline int query1(int rt,int le,int ri)
{
int l=tr[rt].l,r=tr[rt].r;
if(le<=l&&r<=ri)
return tr[rt].mx;
int mid=(l+r)>>1,res=0;
if(le<=mid)
res=max(res,query1(rt<<1,le,ri));
if(mid+1<=ri)
res=max(res,query1(rt<<1|1,le,ri));
return res;
}
inline int query2(int rt,int le,int ri)
{
int l=tr[rt].l,r=tr[rt].r;
if(le<=l&&r<=ri)
return tr[rt].mn;
int mid=(l+r)>>1,res=2e9;
if(le<=mid)
res=min(res,query2(rt<<1,le,ri));
if(mid+1<=ri)
res=min(res,query2(rt<<1|1,le,ri));
return res;
}
inline int solve(double mid)
{
for(int i=1;i<=n;++i)
b[i]=(double)a[i]-(double)i*mid;
double ji=k*mid;
int head=1,tail=0;
for(int i=1;i<=n-L;++i)
{
while(head<=tail&&i+L-q[head]+1>R)
++head;
while(head<=tail&&b[q[tail]]>=b[i])
--tail;
q[++tail]=i;
if(b[i+L]-b[q[head]]>=ji)
return 1;
}
if(n-q[head]+1>L&&b[n]-b[q[head]]>=ji)
return 1;
return 0;
}
inline int check(double mid)
{
if(solve(mid))
return 1;
for(int i=1;i<=(n>>1);++i)
swap(a[i],a[n-i+1]);
if(solve(mid))
return 1;
return 0;
}
int main()
{
scanf("%d",&T);
while(T--)
{
n=read();
k=read();
L=read();
R=read();
for(int i=1;i<=n;++i)
a[i]=read();
build(1,1,n);
ans=0;
for(int i=1;i+L-1<=n;++i)
ans=max(ans,(double)(query1(1,i,i+L-1)-query2(1,i,i+L-1))/(double)(L-1+k));
double l=0.0,r=1000,mid;
while(r-l>=eps)
{
mid=(l+r)/2;
if(check(mid))
{
ans=max(ans,mid);
l=mid+eps;
}
else
r=mid-eps;
}
printf("%.4lf\n",ans);
}
return 0;
}