[BZOJ4476] [JSOI2015] 送礼物 (01分数规划+ST表)

[BZOJ4476] [JSOI2015] 送礼物 (01分数规划+ST表)

题面

给出n,k,l,r和序列a,要求从a中选一段连续的区间[i,j]出来,使得M(i,j)-m(i,j)/(j-i+k)最大,且[i,j]长度在区间[l,r]内。
M(i,j)表示[i,j]中的最大值,m(i,j)表示[i,j]中的最小值。
Ai< =10^8,N,K< = 50,000

分析

看到\(\frac{max(i,j)-min(i,j)}{j-i+k}\)的形式,我们敏感的想到01分数规划。考虑最终答案我们取的最大值和最小值在序列中的位置。

1.如果最大值和最小值在序列中的距离<L,那么用长度为L的区间包含它最优。因为如果区间更长的话,分子不变,分母会变大,不优。这种情况区间长度已经确定,可以不用01分数规划,直接用单调队列\(O(n)\)求即可。类似滑动窗口问题。

2.如果最大值和最小值在序列中的距离>=L,那么它们在区间的左右端点最优。同样如果区间更长的话,分子不变,分母会变大,不优。

按照01分数规划的套路,二分答案mid,判断当前答案是比mid大还是比mid小。如果比mid大,则有
\(|a_i-a_j| \geq mid(j-i+k)\)

(1)$ (a[i]+mid \times i)-(a[j]+mid \times j) \geq mid\times k $,

(区间左端点为最大值)只要暴力枚举i,再在i右侧满足区间长度条件的范围[i+L-1,i+R-1]内求一个\(a[j]+mid \times j\)最小的j即可

(2)$ (a_j-mid \times j)-(a_i-mid \times i) \geq mid \times k ,$

(区间右端点为最大值)只要暴力枚举j,再在j左侧满足区间长度条件的范围[i-L+1,i-R+1]内求一个\(a[j]+mid \times j\)最小的j即可

如果[i,j]内有比a[i]更大的a[k]怎么办?枚举到k的时候就会计入答案,且k为左端点的答案肯定比i优,会覆盖掉i为左端点的不合法情况
用ST表维护最小值即可。

时间复杂度\(O(n \log ^2 n)\),因为每次检查mid都要重新建一次ST表

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath> 
#include<deque>
#define INF 1e9
#define eps 1e-6 
#define maxn 50000
#define maxlogn 20
using namespace std;
inline void qread(int &x){
    x=0;
    int sign=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') sign=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    x=x*sign; 
}
int t;
int n,K,L,R;
int a[maxn+5];


//最大值最小值在两端的情况 
//最大值最小值在区间两端的时候显然更优 
// 01分数规划的套路,二分答案mid
//|a[i]-a[j]| >=mid(j-i+k)
//(1) (a[i]+mid*i)-(a[j]+mid*j)>=mid*k , 只要暴力枚举i,再在i右侧满足区间长度条件的范围内求一个值最小的j
//(2) (a[j]-mid*j)-(a[i]+mid*i)>=mid*k , 只要暴力枚举j,再在j左侧满足区间长度条件的范围内求一个值最小的j
//如果[i,j]内有比a[i]更大的a[k]怎么办?枚举到k的时候就会计入答案,且k为左端点的答案肯定比i优,会覆盖掉i为左端点的不合法情况 
int log_2[maxn+5]; 
double st1[maxlogn+5][maxn+5];//维护a[i]+mid*i的最小值 
double st2[maxlogn+5][maxn+5];//维护a[i]-mid*i的最小值 
inline double max(double a,double b){
    return a>b?a:b;
}
inline double min(double a,double b){
    return a<b?a:b;
}
double query1(int l,int r){
    int k=log_2[r-l+1];
    return min(st1[k][l],st1[k][r-(1<<k)+1]);
} 
double query2(int l,int r){
    int k=log_2[r-l+1];
    return min(st2[k][l],st2[k][r-(1<<k)+1]);
} 
bool check(double mid){
    for(int i=1;i<=n;i++) st1[0][i]=a[i]+mid*i;
    for(int i=1;i<=n;i++) st2[0][i]=a[i]-mid*i;
    for(int j=1;(1<<j)<=n;j++){
        for(int i=1;i+(1<<j)-1<=n;i++){
            st1[j][i]=min(st1[j-1][i],st1[j-1][i+(1<<(j-1))]);
            st2[j][i]=min(st2[j-1][i],st2[j-1][i+(1<<(j-1))]);
        }
    } 
    double ans=-INF;
    for(int i=1;i<=n;i++){
        if(i+L-1<=n) ans=max(ans,a[i]+mid*i-query1(i+L-1,min(i+R-1,n))); 
    }
    for(int i=1;i<=n;i++){//这里的i对应的是上面的j 
        if(i-L+1>=1) ans=max(ans,a[i]-mid*i-query2(max(i-R+1,1),i-L+1));
    }
    return ans>=mid*K;
} 

//区间长度为L的时候,区间最大值不一定在左右端点,也可能成为最大答案 
//直接长度为L的滑块窗口求解 
double sp_solve(){
    deque<int>qmax;
    deque<int>qmin;
    double ans=-INF;
    for(int i=1;i<=n;i++){
        while(!qmax.empty()&&qmax.front()<i-L+1) qmax.pop_front(); 
        while(!qmin.empty()&&qmin.front()<i-L+1) qmin.pop_front(); 
        while(!qmax.empty()&&a[qmax.back()]<a[i]) qmax.pop_back();
        qmax.push_back(i);
        while(!qmin.empty()&&a[qmin.back()]>a[i]) qmin.pop_back();
        qmin.push_back(i);
        ans=max(ans,1.0*(a[qmax.front()]-a[qmin.front()])/(L-1+K));
    }
    return ans;
}
double bin_search(){
    double l=0,r=1000;
    double mid;
    double res=0; 
//  printf("db %d\n",check(0+eps)?1:0);
    while(r-l>=eps){
        mid=(l+r)/2;
        if(check(mid)){
            res=mid;
            l=mid+eps;
        }
        else r=mid-eps;
    }
    return res;
}
int main(){
//  freopen("1.in","r",stdin);
    log_2[0]=-1;
    for(int i=1;i<=maxn;i++) log_2[i]=log_2[i>>1]+1;
    qread(t);
    while(t--){
        qread(n);
        qread(K);
        qread(L);
        qread(R);
        for(int i=1;i<=n;i++) qread(a[i]);
        double ans1=sp_solve();
//      printf("ans1=%.4f\n",ans1);
        double ans2=bin_search();
        double ans=max(ans1,ans2); 
        printf("%.4f\n",ans);
    }

} 

转载于:https://www.cnblogs.com/birchtree/p/11360244.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值