平均数
题目描述
给一个长度为 n n n 的数列,我们需要找出该数列的一个子串,使得子串平均数最大化,并且子串长度 ≥ m \ge m ≥ m 。
输入格式
第一行两个整数 n n n 和 m m m 。
接下来 n n n 行,每行一个整数 a i a_i a i ,表示序列第 i i i 个数字。
输出格式
一个整数,表示最大平均数的 1000 1000 1 0 0 0 倍,如果末尾有小数,直接舍去,不要用四舍五入求整。
样例 #1
样例输入 #1
1 2 3 4 5 6 7 8 9 10 11 10 6 6 4 2 10 3 8 5 9 4 1
样例输出 #1
提示
数据规模与约定
对于 60 % 60\% 6 0 % 的数据,保证 m ≤ n ≤ 1 0 4 m\le n\le 10^4 m ≤ n ≤ 1 0 4 ;
对于 100 % 100\% 1 0 0 % 的数据,保证 1 ≤ m ≤ n ≤ 1 0 5 1 \leq m\le n\le 10^5 1 ≤ m ≤ n ≤ 1 0 5 ,0 ≤ a i ≤ 2000 0\le a_i\le2000 0 ≤ a i ≤ 2 0 0 0 。
思路:1.先找一个基准数字x;这个基准数的找寻可以使用二分。
2.求出数组中每个数字相对于这个基准数字的偏移量数组。
例如:4 -1 -2 8 -1 的数组,假设我某一轮二分答案的mid为4;
则我找到的偏移量数组为:0 -5 -6 4 -5;
3.接下来,求这个偏移量数组的前缀和数组,便于后续的求和处理;
例如:上述数组的前缀和数组为:0 -5 -11 -7 -12
i 从第m位开始遍历到n(数组最后),开始求以 i 为结尾的子串的最大字串和。最大子段和可以转化为前缀和相减的形式。 设sum[i]为序列A的前i项的和 。设s[i]是序列A以A[i]结尾且长度不小于F的最大连续子段和, 那么显然有: s[i] = sum[i] - min{sum[j]}(0<=j<=i-L)
使用minn这个变量记录1~m位置的最小值(因为我们总希望前缀和a[ i ]减去一个尽量小的数字)。但这个过程可以不用再添加一个循环,因为我只需要在一开始,i-m还是0的时候就记录从0号位置,后续随着i 往后移动,每增加一个位置就也相当于放开了i-m号位置,这时判断i-m号位置的元素是不是比前面0i-m-1这个号码范围内的元素的最小值更小即可求出0 i-m范围内的最小值。
附上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <cstdio> #define INF 2147483647 #include <iostream> using namespace std ; const long long MAXN = 100010 ;double a[MAXN], sum[MAXN];long long n, m;int check (double x) { double presum_of_dis[MAXN] = {0 }; for (long long i = 1 ; i <= n; i++) { presum_of_dis[i] = presum_of_dis[i - 1 ]+(a[i]-x); } double minn=INF,maxn=-INF; for (long long i=m;i<=n;i++) { if (minn>presum_of_dis[i-m])minn=presum_of_dis[i-m]; if (presum_of_dis[i]-minn>maxn)maxn=presum_of_dis[i]-minn; } if (maxn>=0 )return 1 ; else return 0 ; } int main () { scanf ("%d%d" , &n, &m); for (long long i = 1 ; i <= n; ++i) scanf ("%lf" , &a[i]); double i= 0 , j = 2000 ,ans; while (i-j <=0.0000000000001 ) { double mid = (i + j) /2 ; if (check(mid)) { ans=mid; i = mid +0.0000001 ; } else j = mid -0.0000001 ; } cout <<(int )(i*1000 )<<endl ; return 0 ; }
Harbin Institute of Technology, Shenzhen 计算机科学与技术 本科在读