中位数
今天又和大家见面了啦~ 依旧是 《算法竞赛进阶指南》的学习哦~
中位数(Median)又称中值,统计学中的专有名词,是按顺序排列的一组数据中居于中间位置的数,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。 (嘿嘿嘿这是从百度找到的中位数的概念复习复习)
下面通过例子瞅瞅叭~
题目:货仓选址
在一条数轴上有 N 家商店,它们的坐标分别为 A1~AN。
现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。
为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
输入
第一行输入整数N。
第二行N个整数A1~AN。1≤N≤100000 1≤Ai≤100000
输出
输出一个整数,表示距离之和的最小值。
样例输入 Copy
4
6 2 9 1
样例输出 Copy
12
思路:
把a[1]到a[n]从小到大进行排序,假设货仓在坐标为x处,在x左侧有p家商店,右侧有q家商店。如果p>q的话,那么货仓每向左移动一个单位,距离之和就会变小p-q
(这里应该好理解滴叭唔就是p>q的时候,向左移动一个单位,对于右边的q家商店,距离都增加q,对于左边的p家商店,距离都减少p,总距离变化q-p,为负值,也就是减小了p-q);
如果p<q,货仓向右移动一个单位,距离之和也会变小;当p=q时,就是最优解啦啦啦~也就是货仓在中位数处
#include <bits/stdc++.h>
using namespace std;
int main()
{
long n,a[100000]={0},m,sum;
scanf("%ld",&n);
for(long i=1;i<=n;i++)
{
scanf("%ld",&a[i]);
}
sort(a+1,a+n+1);
if(n%2==1)
{
m=(n+1)/2;
}
else
{
m=n/2;
}
for(long i=1;i<=n;i++)
{
sum+=abs(a[m]-a[i]);
}
printf("%ld\n",sum);
return 0;
}
瞅瞅另一道经典嘿嘿嘿
题目 七夕祭
背景
七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。于是 TYVJ 今年举办了一次线下七夕祭。Vani 同学今年成功邀请到了 cl 同学陪他来共度七夕,于是他们决定去 TYVJ七夕祭游玩。
题目描述
TYVJ 七夕祭和 11 区的夏祭的形式很像。矩形的祭典会场由 N 排 M 列共计 N×M 个摊
点组成。虽然摊点种类繁多,不过 cl 只对其中的一部分摊点感兴趣,比如章鱼烧、苹果糖、
棉花糖、射的屋……什么的。Vani 预先联系了七夕祭的负责人 zhq,希望能够通过恰当地布
置会场,使得各行中 cl 感兴趣的摊点数一样多,并且各列中 cl 感兴趣的摊点数也一样多。
不过 zhq 告诉 Vani,摊点已经布置完毕了,唯一的调整方式就是交换两个相邻的摊点。两个
摊点相邻,当且仅当他们处在同一行或者同一列的相邻位置上。由于 zhq 率领的 TYVJ 开发
小组成功地扭曲了空间,每一行或每一列的第一个位置和最后一个位置也算作相邻。现在
Vani 想知道他的两个要求最多能满足多少个。在此前提下,至少需要交换多少次摊点。
输入
第一行包含三个整数 N 和 M 和 T。T 表示 cl 对多少个摊点感兴趣。
接下来 T 行,每行两个整数 x, y,表示 cl 对处在第 x 行第 y 列的摊点感兴趣。
对于 30% 的数据,N, M≤100。
对于 70% 的数据,N, M≤1000。
对于 100% 的数据,1≤N, M≤100000,0≤T≤min(NM, 100000),1≤x≤N,1≤y≤M
输出
首先输出一个字符串。如果能满足 Vani 的全部两个要求,输出 both;如果通过调整
只能使得各行中 cl 感兴趣的摊点数一样多,输出 row;如果只能使各列中 cl 感兴趣的摊点
数一样多,输出 column;如果均不能满足,输出 impossible。
如果输出的字符串不是 impossible, 接下来输出最小交换次数,与字符串之间用一
个空格隔开。
样例输入 Copy
2 3 4
1 3
2 1
2 2
2 3
样例输出 Copy
row 1
思路:
- 首先,明确一下:交换左右两个相邻的摊点不会影响到这一行的摊点总数,只会影响这两列的摊点总数 ;交换上下两个相邻的摊点也是一个道理啦
- 所以将这道题分成两部分解答:通过最少次数的左右交换使每列感兴趣的摊点数相等和通过最少次数的上下交换使每行感兴趣的摊位数相等。
- 以使每列感兴趣的摊点数相等为例解释一下啦~(每行相等同理哦):(我直接用代码里的名字了哈方便理解一点哦!)将每列的感兴趣摊位总数记录a[1]–a[m],如果总摊数t不可以被m整除,则不可能达到要求;如果可以被整除,就要让每列都有t/m个摊点。
- 用a[i]-t/m,如果最终全部都变为0,也就是说每列感兴趣的摊位数相等了。
- 如果求出a[i]-t/m的前缀和s1[i],也就是求出了前 i 列还需要得到的摊位数的和,也就是需要转移的次数。这里的前缀和代表的意思一定要好好理解哦!第一列的摊位还缺的数是s1[1],而s1[1]+a[2]-t/m也就是第二列给了第一列它需要的摊位以后,第二列自己需要的摊位数,以此类推,这样想会更简单理解下面。就算a[i]-t/m为负数也没关系,可以调换移动摊位的顺序,在前在后不影响结果。
- 思考思考,觉得,一定存在一种最优解的方案,某两个相邻的摊位之间没有发生交换就可以达到目标。
- 但是不知道是哪两个相邻的摊位啊,就可以假设在第k列和第k+1列的摊位之间不需要交换。
- 题意中每两个相邻的列都可以交换,还加上第一列和最后一列,类似于环状结构。而我们假设的第k列和第k+1列不用交换,就相当于在第k列之后砍了一刀,将环状结构变成了直线状的结构。
- 这样,前缀和数组s1[i]发生的变化就是都减去s1[k]。(这里s1[k]就最好用上面的理解方式啦,这是第k列需要的摊位数,但是它现在没法移动,就减去)
- 所以,所需要的最小交换次数,也就是 | s1[i]-s1[k] | 当i从1到m的和。这里就和上面的题目很像对不对!!s1[i]就是商店位置,s1[k]就是货仓位置,后来的就不用多说了叭嘿嘿嘿~
#include <bits/stdc++.h>
using namespace std;
int main()
{
long n,m,t,a[100000]={0},s1[100000]={0};
long s2[100000]={0},step1,step2,b[100000]={0},x,y;
scanf("%ld %ld %ld",&n,&m,&t);
if(t%m!=0&&t%n!=0)
{
printf("impossible");
}
else
{
for(int i=1;i<=t;i++)
{
scanf("%ld %ld",&x,&y);
a[y]++;//a[y]记录第y列cl感兴趣的摊位总数
b[x]++;//记录第x行的感兴趣摊位总数
}
for(int i=1;i<=m;i++)
{//在a[]-t/m之后的前缀和
s1[i]=(a[i]-t/m)+s1[i-1];
}
for(int i=1;i<=n;i++)
{//在b[]-t/n之后的前缀和
s2[i]=(b[i]-t/n)+s2[i-1];
}
sort(s1+1,s1+m+1);//从小到大排序
for(int i=1;i<=m;i++)
{
if(m%2==1)//中位数的思想
step1+=abs(s1[i]-s1[(m+1)/2]);
else
step1+=abs(s1[i]-s1[m/2]);
}
sort(s2+1,s2+n+1);
for(int i=1;i<=n;i++)
{
if(n%2==1)
step2+=abs(s2[i]-s2[(n+1)/2]);
else
step2+=abs(s2[i]-s2[n/2]);
}
if(t%m==0&&t%n==0)
{
printf("both %ld",step1+step2);
}
else if(t%m!=0&&t%n==0)
{
printf("row %ld",step2);
}
else
{
printf("column %ld",step1);
}
}
return 0;
}