bzoj4476 [Jsoi2015]送礼物
化简式子
$M>=m+ans*(r-l+k)$
发现$M,m$确定时,总区间长度越小越好,于是假定右端点为最小值$M+ans*l>=m+ans*r+ans*k$,
右面都确定了,但最大值仍然有两种情况,一是最大值就在要求的区间内,二是在要求的区间右侧,
对于第一种情况,直接把每个点的val扔进单调队列就可以了,第二种呢,因为要求区间长度最小,所以左端点即为$r-L+1$,单调队列维护$i-L+1~i$的最大值即可,
左端点为最小值也一样。
而且不需要判断在我们选的区间内是否有比$a[i]$更小的,因为那一定比这优,考虑过,
所以每次check直接正反扫就好了。
2017.11.7更新
原程序被hack,因为如果只能全选的话,最小值又不在两边,就不会枚举到最优解,所以强行往两侧各添加n个0即可。
代码已更正,可放心食用
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<cmath> 6 #define N 150050 7 #define eps 1e-6 8 using namespace std; 9 int n,l,r,k; 10 int a[N]; 11 double val[N]; 12 int q1[N],q2[N],head1,head2,tail1,tail2; 13 bool check(double x){ 14 head1=head2=1; 15 tail1=tail2=0; 16 for(int i=1;i<=2*n;i++){ 17 int pos=i-l+1; 18 double now=-1000000000; 19 if(pos>0){ 20 val[pos]=a[pos]+x*pos; 21 while(head1<=tail1&&val[pos]>val[q1[tail1]])tail1--; 22 q1[++tail1]=pos; 23 while(head1<=tail1&&q1[head1]<=i-r)head1++; 24 now=max(now,val[q1[head1]]); 25 } 26 while(head2<=tail2&&a[i]>a[q2[tail2]])tail2--; 27 q2[++tail2]=i; 28 while(head2<=tail2&&q2[head2]<=i-l)head2++; 29 if(i>n&&pos>0){ 30 now=max(now,a[q2[head2]]+x*(i-l+1)); 31 if(now>=a[i]+x*(i+k))return 1; 32 } 33 } 34 head1=head2=1; 35 tail1=tail2=0; 36 for(int i=3*n;i>=n+1;i--){ 37 int pos=i+l-1; 38 double now=-1000000000; 39 if(pos<=3*n){ 40 val[pos]=a[pos]-x*pos; 41 while(head1<=tail1&&val[pos]>val[q1[tail1]])tail1--; 42 q1[++tail1]=pos; 43 while(head1<=tail1&&q1[head1]>=i+r)head1++; 44 now=max(now,val[q1[head1]]); 45 } 46 while(head2<=tail2&&a[i]>a[q2[tail2]])tail2--; 47 q2[++tail2]=i; 48 while(head2<=tail2&&q2[head2]>=i+l)head2++; 49 if(i<=2*n&&pos<=3*n){ 50 now=max(now,a[q2[head2]]-x*(i+l-1)); 51 if(now>=a[i]-x*(i-k))return 1; 52 } 53 } 54 return 0; 55 } 56 int main(){ 57 int T;scanf("%d",&T); 58 while(T--){ 59 scanf("%d%d%d%d",&n,&k,&l,&r); 60 for(int i=n+1;i<=2*n;i++)scanf("%d",&a[i]); 61 double L=0,R=1000,M; 62 while(L+eps<R){ 63 M=(L+R)/2.0; 64 if(check(M))L=M; 65 else R=M; 66 } 67 printf("%0.4lf\n",M); 68 } 69 return 0; 70 }
人生如梦亦如幻 朝如晨露暮如霞。