洛谷T68648
题目背景
为了让俱乐部的孩子们放假在家学习,mxj特地为俱乐部的孩子们准备了一个winter camp, 在举办之前,mxj想让大家热热身,于是让大家报出了自己收到的压岁钱数,并排成了一个正整数序列A。
题目描述
mxj会给你一个长度len,让你从这个整数序列A中找到长度大于等于len且平均数最大的一个连续的子序列。
输入输出格式
输入格式:
第一行,一个正整数N,表示俱乐部人数,一个正整数len,表示子列长度。
接下来N行,每行一个正整数,表示压岁钱。
输出格式:
一个正整数S,表示最大的平均数X∗1000的结果。
输入输出样例
输入样例#1:
10 6
6
4
2
10
3
8
5
9
4
1
输出样例#1:
6500
说明
每个人的收到的压岁钱不超过30000。
0<N≤100000
0<len≤N
题解
1.这道题考察的是二分+动态规划+前缀和。
2.二分搜索法:通过不断缩小解可能存在的范围,从而求得问题最优解的方法。
3.因为我们知道每个人压岁钱的范围,子序列的平均数一定在其中,所以二分的范围可以是压岁钱的范围。我们先枚举一个可能的数,然后数组里的值全部减去这个值,这样就把问题转化为判定是否存在一个长度不小于len的子段,子段和为非负。
4.基础DP中有这样一类题目:求最大连续子序列和。而这道题就可以转化为类似的求长度不小于len的最大连续子序列和,再判断其是否大于0。可以利用前缀和来求,则有
m
a
x
(
A
i
+
1
+
A
i
+
2
+
.
.
.
+
A
j
)
=
m
a
x
(
s
u
m
j
−
m
i
n
(
s
u
m
i
)
)
max(A_{i+1}+A_{i+2}+...+A_j)=max(sum_j-min(sum_i))
max(Ai+1+Ai+2+...+Aj)=max(sumj−min(sumi))
一本书上的代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
double a[N], b[N], sum[N];//原序列,减去mid后的序列,前缀和序列
int main(void)
{
int n, len;
scanf("%d%d", &n, &len);
for (int i = 1; i <= n; i++)
scanf("%lf", &a[i]);
double eps = 1e - 5;
double l = -1e6, r = 1e6;
while(r - l > eps)
{
double mid = (l + r) / 2;
for (int i = 1; i <= n; i++)
{
b[i] = a[i] - mid;
sum[i] = sum[i - 1] + b[i];
}
double ans = -1e10;
double min_val = 1e10;
for (int i = len; i <= n; i++)
{
min_val = min(min_val, sum[i - len]);//最小的子序列和
ans = max(ans, sum[i] - min_val);//最大的子序列和
}
if(ans >= 0)
l = mid;
else
r = mid;
}
printf("%d\n", int(r * 1000));
return 0;
}
一个大佬的题解,思路差不多:https://blog.csdn.net/Coldfresh/article/details/79617912