BZOJ4476 JSOI2015 送礼物 分数规划+单调队列+线段树

题目链接

题意:
给定一个长度为 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} ji+kmax{a[i...j]}min{a[i...j]},求这个最大值。 n < = 50000 n<=50000 n<=50000

题解:
求一个分数的最大值经常是要用到分数规划的。我们先来考虑如果区间选出的最大值和最小值之间距离小于等于 L L L,那么我们肯定想在分子一定的时候分母尽可能小,于是我们要让分母中的 j − i = L j-i=L ji=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(ji+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(ji+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<ji+1<=Rmax{a[i]+midi(a[j]+midj)}>=midk 我们设 a [ i ] + m i d ∗ i = b [ i ] a[i]+mid*i=b[i] a[i]+midi=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<ji+1<=Rmax{b[i]b[j]}>=midk 我们发现这个式子是可以用单调队列维护 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值