[TK] 送礼物

题解引用

引理1: 区间 \([l,r]\) 是最优解的必要不充分条件是: \(l,r\) 分别是区间的最小值与最大值.

这很显然,若假设不成立,当区间向内缩小时,一定有分子不变,分母变小,进而算出更优解,与假设矛盾.

引理2: 最优解不小于 \(x\) 的充要条件为存在区间 \([i,j]\),使得 \((A_{i}-i\times x)-(A_{j}-j\times x )-k\times x\ge 0\)\((A_{i}+i\times x)-(A_{j}+j\times x )-k\times x\ge 0\) .

证明一下引理2:

题设转化为公式表达为:

\[\exist i,j\to\frac{M(i,j)-m(i,j)}{j-i+k}\ge x \]

移项:

\[\exist i,j\to M(i,j)-m(i,j) \ge j\times x-i\times x+k \times x \]

\[\exist i,j\to M(i,j)-m(i,j)-j\times x+i\times x \ge k \times x \]

假设我们已经通过引理1,使区间可能成为最优解,那么公式可以变为:

\[\exist i,j\to \begin{cases}A_{i}-A_{j}-j\times x+i\times x \ge k \times x\ (A_{i} \ge A_{j})\\ A_{j}-A_{i}-j\times x+i\times x \ge k \times x\ (A_{i} \le A_{j})\end{cases} \]

略微整理一下即为上述引理内容.

题目分析

根据题解可以看出来,我们一共有两个引理需要维护.

注意到引理1我们可以通过以下方式使用单调队列快速维护:

  • 遍历不是以端点为阶段,而是以长度为阶段. 即当 \(A_{i} \le A_{j}\) 时,固定左端点,使其始终为单调队列最小值,伸长右端点,不断更新最值,这样即可一次遍历找出所有符合引理1的区间. 另一种情况同理.

注意到题中 \(ans \le 1000\) ,利用引理2,我们可以使用二分答案来枚举最终的答案. 所以我们来梳理一下这道题的总流程:

  1. 二分枚举一个答案值 \(x\).
  2. 根据引理2,我们分别维护 \(A_{i}+i\times x\)\(A_{i}-i\times x\) 的单调的队列,方便进行值 \(x\) 的可行性判断.
  3. 根据引理1,我们通过上述方法找出全部可能为最优解的区间.
  4. 根据引理2,只要有一个区间满足条件,那么值 \(x\) 即为可行的.
  5. 重复上述步骤。直到精度满足要求.

代码实现

double a[5000001],c[5000001];
int main(){
	int t;
	cin>>t;
	while(t--){
		int n,k,L,R;
		cin>>n>>k>>L>>R;
		for(int i=1;i<=n;++i){
			cin>>a[i];
		}
		double l=0,r=1000;
		while(r-l>1e-7){ //1e-7比较玄学,开小了容易超时,开大了达不到精度,还会莫名tle
			double mid=(l+r)/2; //使用循环版本的二分答案
			bool flag=false;
			deque<int> q;
			for(int i=1;i<=n;++i){ //check部分开始
				c[i]=a[i]-1.0*mid*i; //维护a[i]-x*i
			}
			for(int i=L;i<=n;++i){ //枚举区间长度
				while(!q.empty()&&i-q.front()>=R){
					q.pop_front();
				}                               //维护最值
				while(!q.empty()&&c[q.back()]>=c[i-L+1]){
					q.pop_back();
				}
				q.push_back(i-L+1);
				if(c[i]-c[q.front()]>=mid*k){
					l=mid;
					flag=true;   //假如有一个满足的,check成功
					break;
				}
			}
			if(!flag){ //再写一遍另一种情况
				q.clear();
				for(int i=1;i<=n;++i){
					c[i]=a[i]+1.0*mid*i; //维护a[i]+x*i
				}
				for(int i=L;i<=n;++i){
					while(!q.empty()&&i-q.front()>=R){
						q.pop_front();
					}
					while(!q.empty()&&c[q.back()]<=c[i-L+1]){
						q.pop_back();
					}
					q.push_back(i-L+1);
					if(c[q.front()]-c[i]>=mid*k){
						l=mid;
						flag=true;
						break;//check部分结束
					}
				}
				if(!flag){
					r=mid;//二分结束
				}
			}
		}
		double ans=l; //根据答案算答案,写的比较不好,就当个参考
		deque<int> q1,q2;
		for(int i=1;i<=L-1;++i){
			while(!q1.empty()&&a[q1.back()]<=a[i]){
				q1.pop_back();
			}
			while(!q2.empty()&&a[q2.back()]>=a[i]){
				q2.pop_back();
			}
			q1.push_back(i);
			q2.push_back(i);
		}
		for(int i=L;i<=n;++i){
			while(!q1.empty()&&a[q1.back()]<=a[i]){
				q1.pop_back();
			}
			while(!q2.empty()&&a[q2.back()]>=a[i]){
				q2.pop_back();
			}
			q1.push_back(i);
			q2.push_back(i);
			while(!q1.empty()&&i-q1.front()>=L){
				q1.pop_front();
			}
			while(!q2.empty()&&i-q2.front()>=L){
				q2.pop_front();
			}
			if(!q1.empty()&&!q2.empty()){
				ans=max(ans,1.0*(a[q1.front()]-a[q2.front()])/(L+k-1));
			}
		}
		printf("%.4f\n",ans);
	}
}
posted @ 2024-02-23 20:00  HaneDaniko  阅读(41)  评论(1编辑  收藏  举报