POJ 1723 士兵排隊 C語言實現

**

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章