信息学一本通 二分查找
【题目描述】
给定一个长度为n的正整数序列A。求一个平均数最大的,长度不小于L的子序列。
【输入】
第一行,n和L;
n个正整数,表示A。
【输出】
一个整数,表示答案的1000倍(不用四舍五入,直接输出)。
【输入样例】
10 6
6 4 2 10 3 8 5 9 4 1
【输出样例】
6500
【提示】
n ≤ 100000
【分析】
依题意,需要找到一个最大的平均值,此时有一段数量大于等于L的子序列存在。那么可以考虑采用二分查找的方式在平均值范围内尝试查找,这个范围可以估摸为0~1e6。
还需要知道前缀和的知识,后面一个前缀和 - 前面一个前缀和 = 这段子序列的和 。
题目要求子序列最少是L个,则i从L值开始,可以在前缀和sum[0]~sum[i-l]范围内找到一个最小值minn。
依次用前缀和的值 - minn,挑出最大的情况,最终maxx中值表示找到一段子序列和最大的。
即在当前平均值情况下,找到一段子序列和最大。因为前面计算前缀和时已经扣除了平均值,这个时候如果maxx >= 0 说明还可以再多扣一点平均值,平均值可以往大的方向调整,否则平均值需要往小的方向调整。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
序列a | 6 | 4 | 2 | 10 | 3 | 8 | 5 | 9 | 4 | 1 | |
扣除均值 | 3 | 1 | -1 | 7 | 0 | 5 | 2 | 6 | 1 | -2 | |
前缀和 | 0 | 3 | 4 | 3 | 10 | 10 | 15 | 17 | 23 | 24 | 22 |
minn | maxx |
假设指定了一个平均值3,扣除3以后计算样例前缀和如上表,i从6开始查找,minn = 0, maxx = 24,表示1~9这个子序列扣除平均值后的和为24,说明还可以把平均值调大一点。
【完整代码】
#include <bits/stdc++.h>
double a[100005],sum[100005];
using namespace std;
int main(int argc, char *argv[]) {
int n,l;
cin >> n >> l;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
//平均值范围内二分查找
double left = 0, right = 1e6;
while(right - left > 1e-6){
//取中位数
double mid = (left + right) / 2;
//计算前缀和,计算时减去了平均值
for(int i = 1; i <= n; i++){
sum[i] = sum[i-1] + a[i] - mid;
}
//估摸着给一个初值
double minn = 1e10, maxx=-1e10;
/*题目要求子序列最少是L个,则则i从L值开始,可以在前缀和sum[0]~sum[i-l]范围内找到一个最小值minn。
依次用前缀和的值 - minn,挑出最大的情况,最终maxx中值表示找到一段子序列和最大的。
即在当前平均值情况下,找到一段子序列和最大。因为前面计算前缀和时已经扣除了平均值,这个时候如果maxx >= 0 说明还可以再多扣一点平均值,平均值可以往大的方向调整,否则平均值需要往小的方向调整。
*/
for(int i = l; i <=n; i++){
minn = min(minn, sum[i - l]);
maxx = max(maxx, sum[i] - minn);
}
//根据maxx值调整平均值
if(maxx >= 0){
left = mid;
}else{
right = mid;
}
}
//输出
cout << int(right*1000);
return 0;
}