题目链接
一本通:http://ybt.ssoier.cn:8088/problem_show.php?pid=1434。
自己OJ:http://47.110.135.197/problem.php?id=4459。
题目
题目描述
给定一个长度为n的正整数序列A。求一个平均数最大的,长度不小于L的子序列。
输入
第一行,n和L;
n个正整数,表示A。
输出
一个整数,表示答案的1000倍(不用四舍五入,直接输出)。
输入样例
10 6
6 4 2 10 3 8 5 9 4 1
输出样例
6500
数据范围
1 ≤ n ≤ 100,000, 1 ≤ L ≤ n, 0 ≤ Ai ≤ 2000。
题目分析
本题是USACO 2003 Mar. Green原题。
分析
从题目可以知道,我们需要在序列 A 中,找出一个连续的子序列,该子序列的长度不小于 L,使得该子序列的平均数最大。
下面我们来分析一下输入样例,看看如何得出输出样例。
输入样例中我们可以知道, ,这样我们知道子序列的长度可能性有这么几种:6、7、8、9、10。
那么任务就变成,如下几个子任务:
1、找到所有长度为 6 的子序列中的最大值,然后求出平均值。
2、找到所有长度为 7 的子序列中的最大值,然后求出平均值。
3、找到所有长度为 8 的子序列中的最大值,然后求出平均值。
4、找到所有长度为 9 的子序列中的最大值,然后求出平均值。
5、找到所有长度为 10 的子序列中的最大值,然后求出平均值。
6、然后找到上面 5 个平均值的最大值,就是我们的结果。
所有长度为 6 的子序列
从样例我们知道,所有子序列如下:
1、子序列为 6, 4, 2, 10, 3, 8,那么总和为 33 。
2、子序列为 4, 2, 10, 3, 8, 5,那么总和为 32 。
3、子序列为 2, 10, 3, 8, 5, 9,那么总和为 37 。
4、子序列为 10, 3, 8, 5, 9, 4,那么总和为 39 。
5、子序列为 3, 8, 5, 9, 4, 1,那么总和为 30 。
那么最大的平均值为 39/6=6.5。
所有长度为 7 的子序列
从样例我们知道,所有子序列如下:
1、子序列为 6, 4, 2, 10, 3, 8, 5,那么总和为 38 。
2、子序列为 4, 2, 10, 3, 8, 5, 9,那么总和为 41 。
3、子序列为 2, 10, 3, 8, 5, 9, 4,那么总和为 41 。
4、子序列为 10, 3, 8, 5, 9, 4, 1,那么总和为 40 。
那么最大的平均值为 41/7=5.857。
所有长度为 8 的子序列
从样例我们知道,所有子序列如下:
1、子序列为 6, 4, 2, 10, 3, 8, 5, 9,那么总和为 47 。
2、子序列为 4, 2, 10, 3, 8, 5, 9, 4,那么总和为 45 。
3、子序列为 2, 10, 3, 8, 5, 9, 4, 1,那么总和为 40 。
那么最大的平均值为 47/8=5.875。
所有长度为 9 的子序列
从样例我们知道,所有子序列如下:
1、子序列为 6, 4, 2, 10, 3, 8, 5, 9, 4,那么总和为 51 。
2、子序列为 4, 2, 10, 3, 8, 5, 9, 4, 1,那么总和为 46 。
那么最大的平均值为 51/9=5.666。
所有长度为 10 的子序列
从样例我们知道,所有子序列如下:
1、子序列为 6, 4, 2, 10, 3, 8, 5, 9, 4, 1,那么总和为 52 。
那么最大的平均值为 52/10=5.2。
综上所述,我们知道这个最大的数据为 6500。
算法思路
暴力
如同上面的数据分析,就是列出所有组合,外层循环是从 L 到 n,内层循环就是遍历计算出所有长度为外层循环的总和,然后找出最大值。利用前缀和的技巧,可以降低对应的计算复杂度。即使这样,该算法的时间复杂度为 。
由于 n 的最大值为 100,000,自然这样的解决方案最终解决会是 TLE。
二分
使用二分查找,寻找是否存在长度不小于为 L 的子段的平均数大于二分答案。用二分枚举平均值 ave,数组里每个值都减去 ave,看是否有连续的超过 L 长度的区间使得这段区间的总和大于等于 0,如果能找到,那么说明这个平均值可以达到。
先每个 data[i] 减去 ave 得到 a[i],用 dp[i] 表示以 i 为结尾区间连续长度大于等于 L 的最大连续区间和,maxx[i] 表示以 i 为结尾的最大连续区间和,sum[i] 表示 1~i 的价值总和,那么 maxx[i]=max(maxx[i-1]+b[i],b[i]), dp[i]=maxx[i-f+1]+sum[i]-sum[i-f+1],判断是否有一个 i(i >= L)满足 dp[i]>=0。这样我们可以将复杂度变为 O(nlogn)。
代码细节
1、由于数据量比较大,可以使用 scanf 或者快读。可能使用 cin 会导致超时。
2、由于计算平均值会出现浮点数,所以二分中的三个变量(左边界、右边界和中间值)要使用 double。
AC参考代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+2;
double data[MAXN] = {};//保存数组A
double a[MAXN];//减去枚举值后的数组
double sum[MAXN];//前缀和数组
double maxx[MAXN];// i 为结尾的最大连续区间和
double dp[MAXN];// i 为结尾区间连续长度大于等于 L 的最大连续区间和
int main() {
//读入n和l
int n,l;
scanf("%d%d", &n, &l);
//读入数组A
int i;
double left = 2000;//左边界
double right = 1;//右边界
for (i=1; i<=n; i++) {
//注意由于需要使用前缀和,所以第一个数据要设置为0
scanf("%lf", &data[i]);
left = min(left, data[i]);//更新左边界
right = max(right, data[i]);//更新右边界
}
double eps = 1e-6;//精度
while (right-left > eps) {
//计算中间值
double mid = (left+right)/2;
//数组A的每个数据减去这个枚举值,并计算前缀和
for (i=1; i<=n; i++) {
a[i]=data[i]-mid;
sum[i]=sum[i-1]+a[i];
maxx[i] = max(a[i], maxx[i-1]+a[i]);
}
//遍历长度为L到n所有子序列
double ans=sum[l];
for (i=l+1; i<=n; i++) {
dp[i]=maxx[i-l+1]+sum[i]-sum[i-l+1];
if (ans<dp[i]) {
ans=dp[i];
}
}
//修正边界
if (ans>=0) {
left = mid;
} else {
right = mid;
}
}
//输出
printf("%d\n", (int)(right*1000));
return 0;
}
动态规划
待补充。