**
POJ 1723 士兵排队 C语言实现
**
**
原文
**
Description
N soldiers of the land Gridland are randomly scattered around the country.
A position in Gridland is given by a pair (x,y) of integer coordinates. Soldiers can move - in one move, one soldier can go one unit up, down, left or right (hence, he can change either his x or his y coordinate by 1 or -1).
The soldiers want to get into a horizontal line next to each other (so that their final positions are (x,y), (x+1,y), …, (x+N-1,y), for some x and y). Integers x and y, as well as the final order of soldiers along the horizontal line is arbitrary.
The goal is to minimise the total number of moves of all the soldiers that takes them into such configuration.
Two or more soldiers must never occupy the same position at the same time.
Input
The first line of the input contains the integer N, 1 <= N <= 10000, the number of soldiers.
The following N lines of the input contain initial positions of the soldiers : for each i, 1 <= i <= N, the (i+1)st line of the input file contains a pair of integers x[i] and y[i] separated by a single blank character, representing the coordinates of the ith soldier, -10000 <= x[i],y[i] <= 10000.
Output
The first and the only line of the output should contain the minimum total number of moves that takes the soldiers into a horizontal line next to each other.
Sample Input
5
1 2
2 2
1 3
3 -2
3 3
Sample Output
8
翻译
格格兰郡的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
解题思路
这道题主要考察中位数的应用
想要求走的最短路径,则需要知道怎样移动可以使步数最少
我们不妨将 x, y 坐标分开来看,想要移到同一条水平线上, 则需要最终的结果y 相同, x依次相邻, 且如果最左边的人的位置是x[a], 则最右边的人的坐标是x[a+N]
进一步,需要将此问题转化到数学模型。大家在中学阶段一定见过类似于求解 | x - 4 | + | 7 - x | + | 10 - x |最小值的问题,结果应该是当取到所以零点的中位数时,整体的值最小。如果不能直接记住结论则可以进行一个简单的归纳。即, 当只有一个的时候, 取该值,当有两个的时候取任意一个端点, 当有三个的时候则取中间的,有四个的时候则第二个或者第三个都可以。
再回到上文我们可以知道,最终结果的这条水平线的y 坐标应该是 这些 y 值得中位数。因为这个这个问题在 y 方向上的结果应该是 | ( y[i] - mid_y ) | + | ( y[i+1] - mid_y ) | +…+ | ( y[i+N-1] - mid_y ) |的最小值,所以代入中位数即可。
于是我们将 y 坐标进行排序,排序后中间的数即为中位数
y 方向上的步数比较好确定,那 x 轴方向上的呢?
我们不妨先看一下 x 轴上的步数求解的数学公式是什么样子的。我们假设这条线的第一个人的横坐标是A , 由于站成一排后横坐标是连续的, 所以接下来的横坐标依次是 A+1, A+2, … A+N-1;不妨将1 到 N 设置为循环变量 i, 则可以表示为 A+ i , 设每一点的横坐标为 x[ i ], 则对于每一点来说 移动的距离是 | A + i - x[ i ] |, i 从 0 到 N-1 这同样是一个绝对值排序问题,所以也应该我们也需要找到 x 的中位数, 我们为了便于排序, 设一个新的数组为 x[i] = x[i] - i; 经过这一步转换之后原问题则变成 | A - x[ i ] |
然后用一个循环进行相加即可
在处理 x 坐标时我们要知道横坐标的左右相对位置是没有变得,意思是原来相对在左的还是在左, 相对在右的还是在右,只有这样才会让 x 方向上的移动步数最少
总结一下就是现将 y 坐标排序,找到 y 方向上的中位数,将 y 方向上的步数求出来,然后是稍微麻烦一点的 x 轴, 先将x 轴进行一下排序, 现将 x 的坐标进行一下变换,然后再排序找到中位数, 进而累加求和
注意
这道题的空间与时间都不是无限的,可以看到这道题需要多次排序。所以我们就要尤其的注意时间复杂度。一般常用的冒泡排序等会出现超时现象。所以这道题可以使用 C++ STL 的 快速排序, 即 sort函数, 但是要想使用 C语言实现的话就需要找一个空间复杂度较低的方法,我这次采用的是 归并排序, (转载),这个是一个比较稳定且复杂度为 nlog2n的复杂度,用空间换时间,另外题中说-10000 <= x[i],y[i] <= 10000, 也要注意
下面是AC代码
#include <stdio.h> #include <stdlib.h> //包含 malloc 函数 #include <math.h> //包含 abs()函数 #define MAX_soldier 10000 //采用宏定义 //声明归并排序函数原型 void merge(int arr[], int low, int mid, int high); void merge_sort(int arr[], unsigned int first, unsigned int last); int main() { int x[MAX_soldier], y[MAX_soldier], ; int i=0,j=0, total_soldier; // total_soldier 是题目中第一行需要输入的士兵的个数 int mid_x, mid_y, step=0; // mid_x, mid_y 分别表示 x , y 方向上的中位数 //输入数据 scanf("%d",&total_soldier); for(i=0;i<total_soldier;++i) scanf("%d %d",&x[i], &y[i]); //对 x , y 方向上的数据进行归并排序 merge_sort(y,0,total_soldier-1); merge_sort(x,0,total_soldier-1); //求 y 方向上的中位数 mid_y = y[(total_soldier+1)/2-1]; //进行数组转换 for(i=0;i<total_soldier;++i) x[i]-=i; //再次对 x 坐标排序 merge_sort(x,0,total_soldier-1); //求x 方向中位数 mid_x = x[(total_soldier+1)/2-1]; //累加求和 for(i=0; i<total_soldier; ++i) step+=abs(y[i] - mid_y) + abs(x[i] - mid_x); printf("%d",step); return 0; } //定义归并排序函数 void merge(int arr[], int low, int mid, int high) { int i, k; int *tmp = (int *)malloc((high-low+1)*sizeof(int)); //申请空间,使其大小为两个 int left_low = low; int left_high = mid; int right_low = mid + 1; int right_high = high; for(k=0; left_low<=left_high && right_low<=right_high; k++) { // 比较两个指针所指向的元素 if(arr[left_low]<=arr[right_low]) { tmp[k] = arr[left_low++]; } else { tmp[k] = arr[right_low++]; } } if(left_low <= left_high) { //若第一个序列有剩余,直接复制出来粘到合并序列尾 //memcpy(tmp+k, arr+left_low, (left_high-left_low+l)*sizeof(int)); for(i=left_low;i<=left_high;i++) tmp[k++] = arr[i]; } if(right_low <= right_high) { //若第二个序列有剩余,直接复制出来粘到合并序列尾 //memcpy(tmp+k, arr+right_low, (right_high-right_low+1)*sizeof(int)); for(i=right_low; i<=right_high; i++) tmp[k++] = arr[i]; } for(i=0; i<high-low+1; i++) arr[low+i] = tmp[i]; free(tmp); return; } void merge_sort(int arr[], unsigned int first, unsigned int last) { int mid = 0; if(first<last) { mid = (first+last)/2; /* 注意防止溢出 */ /*mid = first/2 + last/2;*/ //mid = (first & last) + ((first ^ last) >> 1); merge_sort(arr, first, mid); merge_sort(arr, mid+1,last); merge(arr,first,mid,last); } return; }