bzoj4476[JSOI2015]送礼物
选择一段区间之后,对答案有影响的只是区间中的最大最小值,因此考虑枚举最大值找最优的最小值.(不需要保证这两个数值是对应的区间内的实际最大最小值,实际最大最小值对答案产生的贡献一定也会另外被枚举到).
由对称性,只考虑最小值在最大值左边的情况.对于下标等于x的数,如果让下标在[x-l+1,x-1]内的数字作为对应的最小值,那么必然需要延伸这个区间使得区间长度达到l.那么对于这种情况,我们查询这个下标范围内的最小数值即可(每个数值对应的区间长度相同).如果让下标和x的差值大于l小于r的数字作为最小值,则不同的数值对应不同的区间长度,且贪心可得对应的区间一定是一端为最大值,一段为最小值.分数规划,二分答案+单调队列判定即可.二分精度要高一点才不会WA….
#include<cstdio> #include<algorithm> using namespace std; const int maxn=50005; int a[maxn],st[maxn][20]; int buf[maxn]; int n,k,l,r; int rmq(int l,int r){ if(l>n||r<1)return 0x7f7f7f7f; if(l<1)l=1; if(r>n)r=n; int j=buf[r-l+1]; return min(st[l][j],st[r-(1<<j)+1][j]); } int q[maxn]; bool check(double ans){ int head=0,tail=0; for(int i=1;i<=n-l;++i){ while(head!=tail&&i+l>q[head]+r-1)head++; while(head!=tail&&(a[q[tail-1]]-q[tail-1]*ans)>(a[i]-i*ans))tail--; q[tail++]=i; if(a[i+l]-(i+l+k)*ans>a[q[head]]-q[head]*ans)return true; } head=0;tail=0; for(int i=n;i>l;--i){ while(head!=tail&&i-l<q[head]-r+1)head++; while(head!=tail&&(a[q[tail-1]]+(q[tail-1]+k)*ans>a[i]+(i+k)*ans))tail--; q[tail++]=i; if(a[i-l]+(i-l)*ans>a[q[head]]+(q[head]+k)*ans)return true; } return false; } int main(){ int tests;scanf("%d",&tests); for(int j=0;(1<<j)<maxn;++j)buf[1<<j]=j; for(int i=3;i<maxn;++i){ if(!buf[i])buf[i]=buf[i-1]; } while(tests--){ scanf("%d%d%d%d",&n,&k,&l,&r); for(int i=1;i<=n;++i)scanf("%d",&a[i]); for(int i=1;i<=n;++i)st[i][0]=a[i]; for(int j=1;(1<<j)<=n;++j){ for(int i=1;i<=n;++i){ st[i][j]=st[i][j-1]; if(i+(1<<j-1)<=n&&st[i+(1<<j-1)][j-1]<st[i][j])st[i][j]=st[i+(1<<j-1)][j-1]; } } double ans=0; for(int i=1;i<=n;++i){ ans=max(ans,(a[i]-min(rmq(i+1,i+l-1),rmq(i-l+1,i-1)))/double(l-1+k)); } double l=0,r=1e4; while(r-l>1e-7){ double mid=(l+r)/2.0; if(check(mid))l=mid; else r=mid; } printf("%.4f\n",max(ans,l)); } return 0; }