一、例题
士兵
格格兰郡的 N 名士兵随机散落在全郡各地。
格格兰郡中的位置由一对 (x,y) 整数坐标表示。
士兵可以进行移动,每次移动,一名士兵可以向上,向下,向左或向右移动一个单位(因此,他的 x 或 y 坐标也将加 1 或减 1)。
现在希望通过移动士兵,使得所有士兵彼此相邻的处于同一条水平线内,即所有士兵的 y 坐标相同并且 x 坐标相邻。
请你计算满足要求的情况下,所有士兵的总移动次数最少是多少。
需注意,两个或多个士兵不能占据同一个位置。
输入格式
第一行输入整数 N,代表士兵的数量。
接下来的 N 行,每行输入两个整数 x 和 y,分别代表一个士兵所在位置的 x 坐标和 y 坐标,第 i 行即为第 i 个士兵的坐标 (x[i],y[i])。
输出格式
输出一个整数,代表所有士兵的总移动次数的最小值。
数据范围
1≤N≤10000,
−10000≤x[i],y[i]≤10000
输入样例:
5
1 2
2 2
1 3
3 -2
3 3
输出样例:
8
题目来源:算法竞赛进阶指南
题目链接:士兵
二、解题思路:
问题让我们取得每个点走到在y轴一条线上并且x的坐标不能重叠的最小步数。根据题意我们可以四个方向走,并且只需记录步数,因此不难发现x和y其实是独立的。那我们根据贪心的思想,只要x走的步数为最小和y的步数最小即可取得答案。
那我们先来分析y走的步数,假设最后y会走到y轴上的点b,则可以推出步数为 |y1 - b| + |y2 - b| + … + |yn - b|。
分析式子,由绝对值不等式可以推出 最短距离点b即为 y 的中位数点。
(^ _^ 知道你没懂什么意思,文末有证明)
再来分析x走的步数,我们试着先将x排个序,并且由于他们最后都会走到一条线上,那我们假设最后x上的点走到了 a,a + 1,a + 2,a + 3 … ,a + n上。那我们就可以推出最小步数即为 |x1 - a| + |x2 - a - 1| + … + |xn - a - n|。
我们整理下上面的式子 |x1 - a| + |x2 - 1 - a| + … + |xn - n - a|,我们可以将(x - n) 看成一个整体,那我们就
可以发现,这个式子和我们刚刚分析的y走的步数完全一致。因此,答案呼之欲出。其实x也很好理解,相当于我们走到了一个新的点,并且让我们求一个点到所有点的最短距离,那这个点就是中位数的点了。
三、题解代码
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 10010;
int x[N],y[N];
int work(int a[],int n)
{
sort(a,a + n);
int res = 0;
for (int i = 0; i < n; i ++ ) res += abs(a[i] - a[n / 2]);
return res;
}
int main()
{
int n,avg = 0;
scanf("%d",&n);
for (int i = 0; i < n; i ++ ){
scanf("%d%d",&x[i],&y[i]);
}
sort(x,x + n);
for (int i = 0; i < n; i ++ ) x[i] -= i;
printf("%d",work(x,n) + work(y,n));
return 0;
}
四、贪心证明:
求数轴中位数所有点的最短距离,假设我们的最短距离点为x,数轴上的点为[a1,an],则最短距离可以表示成
|a1 - x| + |a2 - x| + |a3 - x| + … + |an - x|,我们的目标就是求出这个等式的最小值。
如果数轴上只有两个点a和b,则由绝对值不等式可知|a - x| + |b - x| >= |a - b|必然成立,而当x在a和b之间时,
|a - x| + |b - x| == |a - b|取得最小值。
因此由上面的例子可以将等式|a1 - x| + |a2 - x| + |a3 - x| + … + |an - x|推广,我们让数轴上的左右两端的
值配对,|a1 - x| + |a2 - x| + |a3 - x| + … + |an - x| >= |a1 - an| + |a2 - an - 1| + … + |an / 2 - an / 2 + 1|,则同样的由绝对值不等式,可以推出,当x同时满足在[a1,an]和[a2,an - 1] … 之间时,即x为中位数时,可以取得最短距离。
总结
贪心算法虽然是基础算法,但是大部分题目思维性太过于巧妙。我们遇到训练时碰着贪心题目可以试着证明算法的可行性,可以提高对算法的理解更加深刻。