洛谷P1419 寻找段落
题目描述
给定一个长度为n的序列a_i,定义a[i]为第i个元素的价值。现在需要找出序列中最有价值的“段落”。段落的定义是长度在[S,T]之间的连续序列。最有价值段落是指平均值最大的段落,
段落的平均值=段落总价值/段落长度。
输入格式
第一行一个整数n,表示序列长度。
第二行两个整数S和T,表示段落长度的范围,在[S,T]之间。
第三行到第n+2行,每行一个整数表示每个元素的价值指数。
输出格式
一个实数,保留3位小数,表示最优段落的平均值。
输入输出样例
输入 #1
3
2 2
3
-1
2
输出 #1
1.000
说明/提示
【数据范围】
对于100%的数据有n<=100000,1<=S<=T<=n,-10000<=价值指数<=10000。
题目的大意是,给n个段落的价值,现在要画出一个长度为S到T的区间,使得这个区间内价值平均值最大。看样子是个滑动窗口,但这个区间长度不定又不知道如何处理。
注意到最后是保留三位小数,不禁想到可能是二分答案!答案的单调性是显然的。那么对于一个特定的均值,如何检查是否存在一个长度在S到T的区间满足呢?
假设当前需要判断的均值为x。首先为了简化问题,可以将数组里每个元素减掉x,这样就只需要看是否存在一个区间的和大于0即可。我们知道,滑动窗口处理区间的最大值和最小值可以达到 O ( n ) O(n) O(n),而区间和可以转化成前缀和的差。检查区间和的最大值是否能够大于等于0,就等价于检查s[i]与之前S到T范围内的最小值s之差,是否大于等于0。代码如下:
- a是原数组,s是前缀和
- 二分的区间是价值最低的段落到价值最高的段落
- 这里没有直接在数组上减去mid,而是在前缀和里减,因为之后只需要用到前缀和。注意s要开double。。否则-mid就尴尬了,debug了一会这个问题。。
- q里存的是从i之前S到T范围的前缀和s形成的单调队列(开始特别难以理解q里存的是什么。因为现在是要看是否存在s[i]-s[j]非负,s[i]是当前遍历到i可以直接求到的,而s[j]是要i之前S到T区间内的前缀和,这里只需要取最小值,这正是滑动窗口最小值问题。。)
#include <bits/stdc++.h>
using namespace std;
int n,S,T;
double a[100010],s[100010];
int f(double mid){
s[0]=0;
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]-mid;
deque<int> q;
for(int i=1;i<=n;i++){
if(i>=S){
while(!q.empty()&&s[i-S]<s[q.back()]) q.pop_back();
q.push_back(i-S);
}
if(!q.empty()&&q.front()<i-T) q.pop_front();
if(!q.empty()&&s[i]-s[q.front()]>=0) return 1;
}
return 0;
}
int main(){
cin>>n>>S>>T;
double minv=INT_MAX,maxv=INT_MIN;
for(int i=1;i<=n;i++){
cin>>a[i];
minv=min(minv,a[i]);
maxv=max(maxv,a[i]);
}
double l=minv,r=maxv;
while(r-l>1e-5){
double mid=(l+r)/2;
if(f(mid)) l=mid;
else r=mid;
}
printf("%.3lf",l);
return 0;
}