Skip to content
本博客自 2023 年 4 月 4 日起转为归档状态,可能不再发表新的博文。点此了解博主的竞赛生涯
This blog has been archived by the owner since April 4, 2023. It may no longer have new updates.

S2OJ - 1816. 子集

检测到 KaTeX 加载失败,可能会导致文中的数学公式无法正常渲染。

#题面

#题目描述

定义一个可重数集的价值为:集合中所有数的平均数减去它们的中位数。

现在给定 nn 个数 aia_i ,请你找出这 nn 个数中的一个非空子集,使得这个子集的价值最大。

#输入格式

第一行一个整数 nn 表示数字个数。

第二行 nn 个整数 aia_i

#输出格式

仅一行一个实数表示答案,结果保留 55 位小数。

#输入输出样例

样例输入 #1

6
2 3 3 5 7 8

样例输出 #1

1.66667

样例解释 #1

最优子集为 {3,3,8}\{3, 3, 8\}

#数据范围与约定

  • 对于 20%20\% 的数据,n20n \leq 20
  • 对于 60%60\% 的数据,n2000n \leq 2000
  • 对于 100%100\% 的数据,n2×105n \leq 2 \times 10^50ai1060 \leq a_i \leq 10^6

#思路

由于是选集合,所以先排序。

可以考虑固定住中位数所在的位置。不难发现以下性质:

  1. 最优情况一定选出奇数个数;
  2. 当选取长度为 2k+12k + 1 时,其他的数一定是挑比中位数小的最大的 kk 个与全部数中最大的 kk 个,这样可以使得平均数最大;
  3. 将数字排序,则选取的个数与它的价值是单峰的。

于是就可以二分/三分求解了。

#代码

C++
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <algorithm>
#include <iomanip>
#include <iterator>
#include <numeric>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 2e5 + 5;

int n, a[N], s[N];
double ans;

double calc(int pos, int len) {
return static_cast<double>(s[n] - s[n - len] + s[pos] - s[pos - len - 1]) / (len * 2 + 1);
}

int solve(int x) {
int l = 1,
r = std::min(x - 1, n - x),
res = 0;

while (l <= r) {
int mid = (l + r) >> 1;

if (calc(x, mid - 1) <= calc(x, mid)) {
res = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}

return res;
}

int main() {
std::ios::sync_with_stdio(false);
cin.tie(nullptr);

cin >> n;

std::copy_n(std::istream_iterator<int>(cin), n, a + 1);
std::sort(a + 1, a + 1 + n);
std::partial_sum(a + 1, a + 1 + n, s + 1);

for (int i = 2; i < n; i++) {
ans = std::max(ans, calc(i, solve(i)) - a[i]);
}

cout << std::fixed << std::setprecision(5) << ans << endl;

return 0;
}