Skip to main content

「05」排序应用 - 中位数

最小绝对偏差之和

一组数据到中位数的绝对距离之和是最小的。

如果有一组数 x1,x2,,xnx_1, x_2, \dots, x_n,我们要找一个数 aa,使得 i=1nxia\sum_{i=1}^{n} |x_i - a| 最小,那么这个 aa 就是中位数。

证明

方法一:直观几何法(数轴移动法)

不妨假设 x1x2xnx_1 \le x_2 \le \cdots \le x_n

  1. 考虑两个点的情况: 设有 x1x_1x2x_2
    • 如果 aa[x1,x2][x_1, x_2] 之间,距离之和 x1a+x2a|x_1 - a| + |x_2 - a| 恰好等于线段的长度 x2x1x_2 - x_1(常数)。
    • 如果 aa 在线段外面,距离之和一定会大于线段长度。
  2. 推广到 nn 个点:我们可以“一对一对”地看。为了最小化距离总和,我们要同时满足:
    • aa 在最外层两个点 (x1,xn)(x_1, x_n) 之间;
    • aa 在次外层两个点 (x2,xn1)(x_2, x_{n-1}) 之间;
    • ……依此类推。

结论:如果 nn 是奇数,aa 必须落在最中间的那个点上,即中位数。如果 nn 是偶数,aa 落在中间两个数之间的任何位置(包括端点),距离之和都相等且最小。

从上面的讨论可知,最小值为 (xnx1)+(xn1x2)+(xn2x3)+(x_n - x_1) + (x_{n - 1} - x_2) + (x_{n - 2} - x_3) + \cdots

方法二:代数符号法(扰动法)

假设 aa 是当前的候选值。我们观察当 aa 向右移动一个微小的距离 Δ>0\Delta > 0 时,总距离 S=xiaS = \sum |x_i - a| 会如何变化。

  • NleftN_\text{left}aa 左侧的数据点个数。对于这些点,xia|x_i - a|增加 Δ\Delta
  • NrightN_\text{right}aa 右侧的数据点个数。对于这些点,xia|x_i - a|减少 Δ\Delta
  • 总变化量 ΔS=(NleftNright)Δ\Delta S = (N_\text{left} - N_\text{right}) \cdot \Delta

推导结论:

  • 如果 Nleft<NrightN_\text{left} < N_\text{right}(右边点多),那么 ΔS\Delta S 为负,说明右移能减小总距离。
  • 如果 Nleft>NrightN_\text{left} > N_\text{right}(左边点多),那么左移能减小总距离。
  • 最小值点: 只有当 NleftN_\text{left} 尽可能等于 NrightN_\text{right} 时(即 aa 处于中位数位置),函数停止减小并达到最优。

相关题目

例1. 士兵站队问题

例2. 均分纸牌

均分纸牌

mm 个人排成一行,她们手中分别有 C[1]C[m]C[1] \sim C[m] 张纸牌,在每一步操作中,可以让某个人把自己手中的一张牌交给他旁边的一个人,

我们通过一个具体的例子来说明本题的计算方法。假设有 55 个人,他们手上的牌的数量分别是 8,1,3,11,28, 1, 3, 11, 2

为了均分他们手中的牌,我们首先计算出总共有 2525 张牌,平均下来每个人 55 张,那么第一个人将向右传递 33 张牌,第二个人需要他的右边给他 11 张牌,第三个人需要他的右边给他 33 张牌,最后,第四个人需要向他的右边传递 33 张牌。

如果用正数表示向右边的人传递牌,负数表示右边的人传递过来的牌,那么,前四个人传递牌的数量分别是 331-13-333

按照上面的方法,足以解决本题,我们依次枚举每个人,若第 ii 个人手中的牌数量为 aia_i,平均数 avg,则多了 aiavga_i - avg 的牌,那将这些牌给第 i+1i + 1 人,即 ai+1a_{i + 1} 增加 aiavga_i - avg。注意 aiavga_i - avg 可能为负数,这表示从 ai+1a_{i + 1} 处拿了牌过来。另外,有可能在计算过充,某个 ai+1a_{i + 1} 变为了负数,但也没关系,在实际操作中可以让 i+1i + 1 先从右边拿牌。

参考代码
#include <bits/stdc++.h>
using namespace std;

int n, A[105];
int main() {
cin >> n;
int avg = 0;
for (int i = 1; i <= n; i++) cin >> A[i], avg += A[i];
avg /= n;

int sum = 0;
for (int i = 1; i < n; i++) {
sum += (A[i] - avg != 0);
A[i + 1] += A[i] - avg;
}
cout << sum << endl;
}

后面的分析是为下一题做准备,

设平均牌数数为 avgavg,传递牌之前,每个人手中牌的数量的前缀和为 S[i]S[i],那么,到第 ii 个人的时候,若 S[i]i×avgS[i] \ge i \times \text{avg},那么,那么,第 ii 个人将给与第 i+1i + 1 个人 S[i]i×avgS[i] - i\times \text{avg} 张牌,否则,第 ii 个人将从第 i+1i + 1 个人手中获取 S[i]i×avg|S[i] - i \times \text{avg}| 的牌。所以,总共移动的牌的数量即为 i=1nS[i]i×avg\displaystyle \sum_{i = 1}^n |S[i] - i\times \text{avg}|

如果我们在传递之前,让每个人手中的牌都减少相同的量,这不会影响最终的结果,因此,让每个人手中的牌均减少 avgavg,上面的结果将变为 i=1nS[i]\displaystyle \sum_{i = 1}^n |S[i]|。(这里的 S[i]S[i] 是指减去 avg 以后新的 S[i]S[i],与上面的 S[i]S[i] 不相等。)

例3. 糖果传递

糖果传递

nn 个小朋友坐成一圈,每人有 aia_i 颗糖果。每人只能给左右两人传递糖果。每人每次传递一颗糖果的代价为 11 。求使所有人获得均等糖果的最小代价。

不妨假设某个最优解中每个人只能向位于其右手边的人传递糖果,传递的糖果分别是 a1,a2,,ana_1, a_2, \cdots, a_n,这里,aia_i 可能是负数,表示实际上从位于其右手处的人收到糖果 ai|a_i|。那么,获得均等糖果的代价即为 i=1nai\displaystyle \sum_{i = 1}^n|a_i|。我们可以断言:

断言

必然存在某个最优解,满足 a1ana_1 \sim a_n 中,至少有一项为 00

证明

假设某个最优解中不存在 00,不妨假设这 nn 项中有 xx 项为正,yy 项为负。

xyx \ge y,不妨假设这 xx 项中最小的一项值为 mm。那么,我们将所有项都减少 mm。这依然能够保证所有人都获得均等糖果。同时,xx 个正数项总共减少了 xmxm,这 xx 个正数项的绝对值之和减少了 xmxmyy 个负数项总将就减少了 ymym,它们的绝对值之和增加了 ymym。所有的 nn 项绝对值之和总共减少了 (xy)m(x - y)m,因为 xyx \ge y,可以得知新的解不会比原来的解差。我们得到了一个包含有一项为 00 的最优解。

xyx \le y 同理。


如果我们通过枚举没有发生传递的位置来计算最优解,那么时间复杂度为 O(n2)O(n^2),无法通过本题。

将每个人手中的糖果数减去平均值,求出前缀和数组为 S[N]S[N]。假设第 kk 个人和第 k+1k + 1 个人之间没有发生传递,那么,从第 k+1k + 1 开始,新的前缀和为

序号前缀和
k+1k + 1S[k+1]S[k]S[k + 1] - S[k]
k+2k + 2S[k+2]S[k]S[k + 2] - S[k]
k+3k + 3S[k+3]S[k]S[k + 3] - S[k]
\vdots\vdots
nnS[n]S[k]S[n] - S[k]
11S[n]+S[1]S[k]S[n] + S[1] - S[k]
22S[n]+S[2]S[k]S[n] + S[2] - S[k]
33S[n]+S[3]S[k]S[n] + S[3] - S[k]
\vdots\vdots
kkS[n]+S[k]S[k]S[n] + S[k] - S[k]

注意到我们已经将每个位置手中的糖果数量减去了平均值,那么 S[n]=0S[n] = 0,上面的表格变为

序号前缀和
k+1k + 1S[k+1]S[k]S[k + 1] - S[k]
k+2k + 2S[k+2]S[k]S[k + 2] - S[k]
k+3k + 3S[k+3]S[k]S[k + 3] - S[k]
\vdots\vdots
nnS[n]S[k]S[n] - S[k]
11S[1]S[k]S[1] - S[k]
22S[2]S[k]S[2] - S[k]
33S[3]S[k]S[3] - S[k]
\vdots\vdots
kkS[k]S[k]S[k] - S[k]

按照第二题推导的结论,总共的代价为每个位置前缀和的绝对值之和,而代价的最小值什么时候取到呢?当 S[k]S[k]S[1]S[1]S[2]S[2]\cdotsS[n]S[n] 的中位数的时候取得。

例4. 七夕祭

例5. 动态中位数

动态中位数

依次读入一个整数序列,每当已经读入的整数个数为奇数时,输出已读入的整数构成的序列的中位数。

我们建立两个二叉堆,一个小根堆,一个大根堆,设当前序列长度为 MM,我们始终保持:

  • 序列中从小到大排名为 1M/21 \sim M/2 的整数存储在大根堆中;
  • 序列中从小到大排名为 M/2+1MM/2 + 1 \sim M 的整数存储在小根堆中。(无论 MM 奇、偶,中位数均位于小根堆堆顶)

当新进来一个数 XX。 若 XX 小于中位数,则将 XX 插入大根堆,否则插入小根堆。然后检查并维护上面的性质。