送礼物(二分答案+单调队列)

QUESTION:
JYY和CX的结婚纪念日即将到来,JYY来到萌萌开的礼品店选购纪念礼物。萌萌的礼品店很神奇,所有出售的礼物都按照特定的顺序都排成一列,而且相邻的礼物之间有一种神秘的美感。于是,JYY决定从中挑选连续的一些礼物,但究竟选 哪些呢?假设礼品店一共有\(N\)件礼物排成一列,每件礼物都有它的美观度。排在第\(i\)(\(1\leq i \leq N\))个位置的礼物美观度为正整数\(A_i\)。JYY决定选出其中连续的一段,即编号为礼物\(i\),\(i+1\),....,\(j-1\),\(j\)的礼物。选出这些礼物的美观程度定义为:

(\(M(i,j)\)-\(m(i,j)\))\(\div\)\((j-i+k)\)

其中\(M\)\((\)\(i\)\(,\)\(j\)\()\)表示\(max\){\(A_i\),\(A_i+1\)\(,\)\(....\)\(A_j\)}
\(m\)\((\)\(i\)\(,\)\(j\)\()\)表示\(min\){\(A_i\),\(A_i+1\)\(,\)\(....\)\(A_j\)}\(,\)\(k\)为给定的正整数。

由于不能显得太小气,所以\(JYY\)\(所\)选礼物的件数最少为\(L\)件;同时,选得太多也不好拿,因此礼物最多选\(R\)件。\(JYY\)应该如何选择,才能得到最大的美观程度?由于礼物实在太多挑花眼,\(JYY\)打算把这个问题交给会编程的你。

输入格式

本题每个测试点有多组数据。
输入第一行包含一个正整数\(T\)(\(T \leq 10\)),表示有\(T\)组数据。
每组数据包含两行.
第一行四个非负整数\(N\),\(K\),\(L\),\(R\)(\(2\leq L\leq R\leq N\))。
第二行包含N个正整数,依次表示\(A_1,A_2....A_n\)(\(A_i\leq 10^8\))\(N,K\leq 50,000\)

输出格式

输出\(T\)行,每行一个非负实数,依次对应每组数据的答案,数据保证答案不会超过\(10^3\)。输出四舍五入保留\(4\)位小数。
解答:
作为比赛压轴,只有1人20分,其余10分,看了一下,还都是打表,也不知道老师为什么没有禁掉QWQ
PS:我们的AKDREAM同学这题由于每组数据没有重新初始化,送走了20分QAQ
但这道题不打表有一个明显的做法,枚举长度,从\(L\)开始枚,记录运算了多少次,然后超过4*\(10^7\)就退出(否则TLE),可以拿20分_
可以简单的发现,如果不考虑礼物个数的话,选出的区间应该两端为最大值与最小值(否则可以将不是最值的一端向里"缩",到最值为止,此时\(M(i,j)\),\(m(i,j)\)不变,\((j-i+k)\)变小,总体变大.
考虑到题目限制,需要先枚举长度为\(L\)的情况,用ST表或单调队列均可,这里选用单调队列.
之后可以采用二分答案mid,每一次a[L],a[R]一个最大,一个最小.,则max((A[i]−i∗mid)−(A[j]−j∗mid)−k∗mid)与max((A[i]+i∗mid)−(A[j]+j∗mid)−k∗mid)有一个大于等于0即可,原因很简单,不作说明.
其中A[i]−i∗mid,A[i]+i∗mid可以用单调队列维护.




上代码:

//JZC de 工业化代码

#include <bits/stdc++.h>
#define ll long long
#define MAX 50001
using namespace std;
ll t,n,k,l,r,head1,head2,tail1,tail2,head,tail;
ll q[MAX],q1[MAX],q2[MAX],a[MAX];
double cnt[50005],ans,Ans;
void I_P1(int i){
	while(head<=tail&&i>=q[head]+(r-l))head++;
	while(head<=tail&&cnt[q[tail]]>=cnt[i])tail--;
	q[++tail]=i;
}
void I_P2(int i){
	while(head<=tail&&q[head]-i>=r-l)head++;
	while(head<=tail&&cnt[q[tail]]>=cnt[i])tail--;
	q[++tail]=i;
}
bool check(double mid) {
    for(int i=1;i<=n;i++)cnt[i]=a[i]-mid*i;
    head=1;tail=0;Ans=-100000000.0;
    for(int i=1;i<=n-l;i++)I_P1(i),Ans=max(Ans,cnt[i+l]-cnt[q[head]]);
    for(int i=1;i<=n;i++)cnt[i]=a[i]+mid*i;
    head=1;tail=0;
    for(int i=n;i>l;i--)I_P2(i),Ans=max(Ans,cnt[i-l]-cnt[q[head]]);
    return Ans>=k*mid;
}
void Push(int i){//更新,将i更新至单调队列中
	while(head1<=tail1&&a[q1[tail1]]>=a[i])tail1--;
	q1[++tail1]=i;
	while(head2<=tail2&&a[q2[tail2]]<=a[i])tail2--;
	q2[++tail2]=i;
}
void Update(int i){//更新,将长度超标的弹出队列
	while(head1<=tail1&&i>=q1[head1]+l)head1++;
	while(head2<=tail2&&i>=q2[head2]+l)head2++;
}
int main() {
    cin>>t;
    while (t--) {
        cin>>n>>k>>l>>r;
        for(int i=1;i<=n;i++)cin>>a[i];
        head1=head2=1;
	tail1=tail2=0;
        //---计算长度为L的最大值---BEGIN 
        for(int i=1;i<l;i++)Push(i);
        ans=-1e9;
        for (int i=l;i<=n;i++) {
        	Update(i),Push(i);
            ans=max(ans,((double)(a[q2[head2]]-a[q1[head1]]))/(double)(l+k-1));
        }
        //---计算长度为L的最大值---END
        //---二分答案---BEGIN
        double l=0.0,r=10000.0;
        while(r>l+(1e-9)){
            double mid=(l+r)/2;
            if(check(mid))ans=max(ans,mid),l=mid;//更新
            else r=mid-(1e-7);
        }
        //---二分答案---END
        printf("%.4lf\n", ans);
    }
}

$ Tips$:
1.队列较多,千万不要搞混,血的教训QAQ
2.二分时每次需要-(1e-7),不然会玄学RE

posted @ 2019-08-25 11:57  鸡中翅  阅读(326)  评论(0编辑  收藏  举报