题意
给定一个长度为\(n\)的序列,你要从中选择一些数字。要求相邻两个数字必须至少选择一个。求:
- 选择出来的数字平均值最大可能是多少
- 选择出来的数字中位数最大可能是多少
数据范围
\(2 \leq n \leq 100000\)
思路
考虑二分查找答案。
- 对于平均值,假设当前查找的答案是\(K\),那么对序列的每个元素减去\(K\)。然后问题转化为是否能从序列中选择一些数字,使得这些数字之和是否非负。
- 对于中位数,假设当前查找的答案是\(K\),那么对序列的每个元素,如果元素大于等于\(K\),那么设为\(1\);如果小于\(K\),那么设为\(-1\)。然后问题转化为是否能从序列中选择一些数字,使得这些数字之和大于\(0\)。
对于以上两个问题,其实就是从序列中选出一些数字,这些数字之和的最大值可能是多少。对于这个问题,考虑DP。设\(f_{i, 0}\)表示从前\(i\)个元素中选,且第\(i\)个元素必须选,所选元素和的最大值;\(f_{i, 1}\)表示从前\(i\)个元素中选,且第\(i\)个元素不选,所选元素和的最大值。那么转移方程就是:\(f_{i, 0} = \max\{f_{i - 1, 0}, f_{i - 1, 1}\}\),\(f_{i, 1} = f_{i - 1,1}\)。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 100010;
const double eps = 1e-6;
int n;
ll a[N];
double b[N];
int c[N];
double f[N][2];
int g[N][2];
bool check1(double x)
{
for(int i = 1; i <= n; i ++) b[i] = a[i] - x;
for(int i = 1; i <= n; i ++) {
for(int j = 0; j < 2; j ++) {
f[i][j] = 0;
}
}
f[1][0] = b[1];
for(int i = 1; i <= n; i ++) {
f[i][0] = max(f[i - 1][0], f[i - 1][1]) + b[i];
f[i][1] = f[i - 1][0];
}
return max(f[n][0], f[n][1]) >= eps;
}
bool check2(int x)
{
for(int i = 1; i <= n; i ++) {
if(a[i] < x) c[i] = -1;
else c[i] = 1;
}
for(int i = 1; i <= n; i ++) {
for(int j = 0; j < 2; j ++) {
g[i][j] = 0;
}
}
g[1][0] = c[1];
for(int i = 1; i <= n; i ++) {
g[i][0] = max(g[i - 1][0], g[i - 1][1]) + c[i];
g[i][1] = g[i - 1][0];
}
return max(g[n][0], g[n][1]) > 0;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
double l = 1, r = 1e9;
while(r - l > eps) {
double mid = (l + r) / 2;
if(check1(mid)) l = mid;
else r = mid;
}
printf("%.6f\n", r);
int l2 = 1, rr = 1e9;
while(l2 < rr) {
int mid = l2 + rr + 1 >> 1;
if(check2(mid)) l2 = mid;
else rr = mid - 1;
}
printf("%d\n", rr);
return 0;
}