中位数技巧(推理+证明)

在有序序列中,中位数具有一些很优美的性质。

定义

中位数,又称中点数,中值。中位数是按顺序排列的一组数据中居于中间位置的数,即在这组数据中,有一半的数据比他大,有一半的数据比他小,这里用 来表示中位数。(注意:中位数和众数不同,众数指最多的数,众数有时不止一个,而中位数只能有一个。) 有一组数据: 将它按从小到大的顺序排序为:X1,X2,X3…Xn 则当N为奇数时 mid=X(n+1)/2;当N为偶数时 mid=(X(n/2)+x(n/2+1))/2 。 一个数集中最多有一半的数值小于中位数,也最多有一半的数值大于中位数。如果大于和小于中位数的数值个数均少于一半,那么数集中必有若干值等同于中位数。

平均数,中位数,众数区别联系

1)平均数是通过计算得到的,因此它会因每一个数据的变化而变化。
2)中位数是通过排序得到的,它不受最大、最小两个极端数值的影响。部分数据的变动对中位数没有影响,当一组数据中的个别数据变动较大时,常用它来描述这组数据的集中趋势。
3)众数也是数据的一种代表数,反映了一组数据的集中程度.日常生活中诸如“最佳”、“最受欢迎”、“最满意”等,都与众数有关系,它反映了一种最普遍的倾向。

优缺点: 平均数:需要全组所有数据来计算;易受数据中极端数值的影响。中位数:仅需把数据按顺序排列后即可确定;不易受数据中极端数值的影响。众数:通过计数得到;不易受数据中极端数值的影响。

典例

1.货仓选址 传送门

在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN。

现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。

为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。

输入格式

第一行输入整数 N。

第二行 N 个整数 A1∼AN。

输出格式

输出一个整数,表示距离之和的最小值。

数据范围

1≤N≤100000,
0≤Ai≤40000

输入样例:

4
6 2 9 1

输出样例:

12

思路:
排序后找中位数,就这么简单
设仓库位置为k
答案为 ans = min{ | X1 - k | + | X2 - k | + | X3 - k |+…+ | Xn - k | }

证明:
这道题目中,每一个点到中位数的距离,都是满足全局的最有性,而不是局部最优性。
设在仓库建在 X 轴坐标处,X 左侧的商店有 P 家 ,右侧的商店有 Q 家
若 P < Q ,则每把仓库的选址向右移动 1 单位距离,距离之和就会变小 Q - P。
同理,若 P > Q , 则仓库的选址向左移动 1 单位距离,距离之和就会变小。
P = Q 时为最优解。

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N];
int main()
{
    int n;
    cin >> n;
    for(int i = 0;i < n;i ++) cin >> a[i];
    sort( a , a + n );
    int res = 0;
    for(int i = 0;i < n;i ++) res + = abs( a[i] - a[n >> 1] );
    cout << res;
    return 0;
}

注意:
abs(a[i] - a[n >> 1]) 改为 abs(a[i] - a[i >> 1]) 也可以 AC
很神奇,这并不是数据的锅,我们可以证明排序之后,有
在这里插入图片描述
证明如下
在这里插入图片描述
同理偶数也成立。

2.糖果传递 传送门

有 n 个小朋友坐成一圈,每人有 a[i] 个糖果。

每人只能给左右两人传递糖果。

每人每次传递一个糖果代价为 1。

求使所有人获得均等糖果的最小代价。

输入格式

第一行输入一个正整数 n,表示小朋友的个数。

接下来 n 行,每行一个整数 a[i],表示第 i 个小朋友初始得到的糖果的颗数。

输出格式

输出一个整数,表示最小代价。

数据范围

1≤n≤1000000,
0≤a[i]≤2×109,
数据保证一定有解。

输入样例:
4
1
2
5
4

输出样例:

4

思路:
假设标号为i的小朋友开始有Ai颗糖果,Xi表示第i个小朋友给了第i-1个小朋友Xi颗糖果,如果Xi<0,说明第i-1个小朋友给了第i个小朋友Xi颗糖果,X1表示第一个小朋友给第n个小朋友的糖果数量。 所以最后的答案就是ans=|X1| + |X2| + |X3| + ……+ |Xn|。

对于第1个小朋友:
A1-X1+X2=ave

-> X2 = ave - A1 + X1 = X1 - C1
(假设C1 = A1 - ave,下面类似)

对于第2个小朋友:
A2-X2+X3=ave

-> X3 = ave - A2 + X2 = 2ave - A1 - A2 + X1 = X1 - C2

即C2 = A1 + A2 - 2ave = A2 + C1 - ave

以此类推
对于第3个小朋友:
A3-X3+X4=ave

-> X4 = ave - A3 + X3 = 3ave - A1 - A2 - A3 + X1 = X1 - C3

可以推出

C[ i ]= C[ i-1 ] + A[ i ] - ave

所以变成找中位数 X1
ans=| X1 | + | X1-C1 | + | X1-C2 | + ……+ | X1-Cn-1 |

#include<iostream>
#include<algorithm>
using namespace std;
typedef  long long ll;
const int N=1000010;
int n;
int a[N],c[N];
ll ave,sum;
int main()
{
    scanf("%d", &n );
    for(int i = 1;i <= n;i ++)
    {
        scanf("%d", &a[i] );
        sum += a[i];
    }
    
    ave = sum / n;

    for( int i = 2;i <= n;i ++ )
       c[i] = c[i-1] + a[i] - ave;
       
    sort( c + 1,c + n + 1 );

    ll ans=0;
    int mid = c[( n >> 1 ) + 1 ];
    
    for(int i = 1;i <= n;i++ )
       ans += abs( c[i] - mid );
       
    printf("%lld ", ans ); 
    
    return 0;
}

3.均分纸牌 传送门

有N堆纸牌,编号分别为 1,2,…,N。

每堆上有若干张,但纸牌总数必为 N 的倍数。

可以在任一堆上取若干张纸牌,然后移动。

移牌规则为:在编号为 1 的堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N−1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

例如 N=4,4 堆纸牌数分别为:(9,8,17,6)。

移动 3 次可达到目的:

从第三堆取四张牌放入第四堆,各堆纸牌数量变为:(9,8,13,10)。 从第三堆取三张牌放入第二堆,各堆纸牌数量变为:(9,11,10,10)。 从第二堆取一张牌放入第一堆,各堆纸牌数量变为:(10,10,10,10)。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,A1,A2,…,AN 表示各堆的纸牌数量。

输出格式

输出使得所有堆的纸牌数量都相等所需的最少移动次数。

数据范围

1≤N≤100,
1≤Ai≤10000

输入样例:

4
9 8 17 6

输出样例:

3

思路:
在有解时,我们可以先考虑第一个人:
设第i 人手里牌为s[i]总数为 T,人数为 M
1.若 s[1] > T / M ,则第一人需要给第二个人 s[1] - T / M 张纸牌,即把 s[2]加上 s[1] - T / M
2.若 s[1] < T / M ,则第一人需要从第二个人 手里拿 T / M - s[1] 张纸牌,即把 s[2] 减去 T / M - s[1]
我们按照同样的方法依次考虑第2~M个人。即使在某个时刻有某个 s[i] 被减为负数也没关系,因为接下来 s[i] 就会从 s[i+1] 处拿牌,在实际中可认为 s[i]s[i+1] 处拿牌发生在s[i-1]s[i] 处拿牌之前。
按照这种方法,经过计算最小步数是:
在这里插入图片描述
s[n]为1~n的前缀和
注意,如果是一次移动一张牌,那么上式和就是答案
但这里是移动若干张,要计算迭代的次数

#include <iostream>

using namespace std;

const int N = 110;

int a[N];
int n, all, cnt;

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> a[i], all += a[i];

    int avg = all / n;               

    for (int i = 0; i < n-1 ; i ++ )
    {
        if (a[i] != avg)
        {
            a[i + 1] += a[i] - avg;     
            cnt ++ ;
        }
    }

    cout << cnt << endl;

    return 0;
}

欢迎点赞与评论~
记得收藏

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值