题目描述
求一个长度为 N N 的序列中长度不超过 的子段,使得这个子段的平均值最大,输出这个平均值的 1000 1000 倍。
算法分析
最大化平均值,二分答案,将序列中的每个元素减去当前判断的平均值,则当前判断的结果就是是否存在一个新序列中的子段,使得这个子段的和不小于 0 0 。
将子段拆成前缀和的形式,枚举右端点,由于可选范围递增,再维护一个变量用来储存当前可选范围中前缀和最小的右端点,统计分别以每个点为右端点所形成的最大子段和,判断它是否大于或等于 。
实现时不知道为什么,限定二分循环次数会 Wrong Anwer
,只能手动设置精度,还有最后要输出右端点 r
,有没有人知道为什么啊?求助。
【Update】找到原因啦!!!
可能是因为卡精度,如果在最后输出的时候加上一些精度的处理就能AC了(保留三位小数所以 eps
取
10−5
10
−
5
),代码见下面的代码二。
代码实现
代码一:
#include <cstdio>
#include <climits>
#include <algorithm>
const int maxn=100005;
int n,f;double arr[maxn],temp[maxn],sum[maxn];
bool check(double num) {
for(int i=1;i<=n;++i) {
temp[i]=arr[i]-num;
sum[i]=sum[i-1]+temp[i];
}
double mininum=1e10,ans=-1e10;
for(int i=f;i<=n;++i) {
mininum=std::min(mininum,sum[i-f]);
ans=std::max(ans,sum[i]-mininum);
}
return ans>=0;
}
int main() {
scanf("%d%d",&n,&f);
double l=1e10,r=-1e10;
for(int i=1;i<=n;++i) {
scanf("%lf",&arr[i]);
l=std::min(l,arr[i]);
r=std::max(r,arr[i]);
}
while(r-l>1e-5) {
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%d\n",int(r*1000));
return 0;
}
代码二:
#include <cstdio>
#include <climits>
#include <algorithm>
const int maxn=100005;
int n,f;double arr[maxn],temp[maxn],sum[maxn];
bool check(double num) {
for(int i=1;i<=n;++i) {
temp[i]=arr[i]-num;
sum[i]=sum[i-1]+temp[i];
}
double mininum=1e10,ans=-1e10;
for(int i=f;i<=n;++i) {
mininum=std::min(mininum,sum[i-f]);
ans=std::max(ans,sum[i]-mininum);
}
return ans>=0;
}
int main() {
scanf("%d%d",&n,&f);
double l=1e10,r=-1e10,mid;
for(int i=1;i<=n;++i) {
scanf("%lf",&arr[i]);
l=std::min(l,arr[i]);
r=std::max(r,arr[i]);
}
for(int i=0;i<100;++i) {
mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%d\n",int(l*1000+1e-5));
return 0;
}