分治算法--士兵排隊(poj1723)


 【問題描述】 

在一個划分成網格的操場上,n個士兵散亂地站在網格點上。網格點由整數最表(x,y)表示。士兵可以沿着網格邊上、下、左、右移動一步,但在同一時刻一個網格上只能有一名士兵。按照軍官的命令,士兵們要整齊地列成一個水平隊列,即排列成(x,y),(x+1,y),…,(x+n-1,y)。如何選擇x,y的值,才能使士兵們以最少的總移動步數排成一列。 
請計算使所有士兵排成一行需要的最少移動步數。

【輸入格式】 
第1行是士兵總數n。接下來的n行是士兵的初始位置,每行兩個整數x和y。

【輸出格式】 
輸出士兵排成一行需要的最少移動步數。

【輸入樣例】

5
1 2
2 2
1 3
3 -2
3 3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

【輸出樣例】

8
      
      
     
    
   
   
  • 1

【數據范圍】 
1<=n<=10000 
-10000<=x,y<=10000

問題分析:

通過適當的移動順序和移動路線可以使得同一時刻不會有兩名士兵站在同一點。

題目要求移動的最少步數

題目要求可轉化為求士兵站立的“最終位置”,即如何取“最終位置”使得士兵移動的步數最少
1. Y軸方向上的考慮(找出Y0的值)
    設目標坐標為Y0,即n個士兵最終需要移動到的Y軸的坐標值為M
  n個士兵的Y軸坐標分別為:
     Y1,Y2 …… …… Yn
移動步數

S=|Y1-Y0|+|Y2-Y0|+ …… …… +|Yn-Y0|
結論:Y0取所有Yi的中間值時可以使得S達到最小。

(可以證明)

中位數是Y0;

解決辦法

1.對所有的Y軸坐標進行排序(Onlogn))或者進行線性時間選擇(On))然后取中間點的Y軸坐標值作為最佳位置Y0的值
 2.通過公式求出Y軸方向上移動的最優步數


2. X軸方向上的考慮
 1首先需要對所有士兵的X軸坐標值進行排序
  2然后,按從左至右的順序依次移動到每個士兵所對應的最終位置(最優),所移動的步數總和就是X軸方向上需要移動的步數
  設排序后
n個士兵在X軸坐標為: 

X1’X2’ …… …… Xn’

他們最終位置X軸坐標值為:

X0X0+1X0+2 …… …… X0+n-1
則所求移動的步數
    S=|X1’-X0| + |X2’-(X0+1)|+ …… +|Xn’-(X0+(n-1)|
經過變形

S=|X1’-X0|+ |(X2’-1)-X0|+ …… +|(Xn’-(n-1))-X0|
注意到公式的形式與Y軸方向上的考慮一樣,同樣是n個已知數分別減去一個待定數后取絕對值,然后求和

問題轉化為:

求出x1’,x2’-1,…Xn’-(n-1)的中位數,即求得X0值,最后算出最優解。


中位數尋找的快速算法

一般尋找中位數可以先將數組排序,按照次序將中間的數據作為中位數即可,其時間復雜度主要取決於排序算法的時間復雜度,利用快速排序可以將其控制為線性對數量級。 

但是能否打破線性對數的限制呢?其中最關鍵的問題是,尋找中位數並不需要整個數組完全有序,如果把多余的元素排序省略掉,那么就可以超越線性對數的限制實現最快的算法。 

啟發來源於快速排序算法中的切分法,比如我們需要找到數組中第 k小的元素,首先將數組a[lo,hi]切分返回整數j,使得a[lo,j-1]都小於等於a[j],而a[j+1,hi]都大於等於a[j],如果j==k,那么j位置的元素就是我們要找的第k小的元素,而如果j>k,就要切分左子數組,如果j<k,就要切分右子數組,不斷縮小選定的子數組的規模直到只剩下一個元素,則它就是最終我們要找的第k小的元素。 

經過數學推導,這種快速切分法尋找中位數僅僅為線性量級,是尋找中位數最為快速的算法。




int partition(int *a, int low, int high)
//分治算法中的分
{ int X0ey = a[low];
while(low<high)
{ while(low < high && a[high] >= X0ey)
high--;
a[low] = a[high];
while(low<high && a[low] <= X0ey)
low++;
a[high]=a[low];
}
a[low]=X0ey;
return low;
}





// 找出數組中第k小的元素,非遞歸實現
int select1(int *a, int k,int n) {
int lo = 0;
int hi = n-1;
//找到最小的開始元素,以及終點元素;
while (hi > lo) {
int j = partition(a, lo, hi);//先是進行分的思想;返回每一輪中已經排好的數的位置;從0開始;

if(j == k){//如果已經排好的數的位置的數剛好是第k小的數;就直接返回,
return a[k];
}
else if(j > k) {//如果是已經排好數的位置坐標大於k,表示將從大的一邊繼續進行查找;
hi = j - 1;
}
else if(j < k) {//如果是已經排好數的位置坐標小於k,表示將從小的一邊繼續進行查找;
lo = j + 1;
}
}
return a[k];
}

// 找出數組中第
int select2(int a[], int k, int lo, int hi) {
int j = partition(a, lo, hi);

if (j == k) {
return a[k];
} else if (j > k) {
return select2(a, k, lo, j - 1);
} else {
return select2(a, k, j + 1, hi);
}
}


int main()
{
int a[10] = {2,1,3,4,7,9,8,10,5,11};
cout<<select1(a, 9,10)<<endl;
cout<<select2(a, 9,0,9)<<endl;


return 0;
}

題目代碼:

這是沒用上面的找中位數的方法寫的,在poj上能過,不過時間稍稍有點長;

#include <iostream>
#include <cstring>
#include <cmath>
#include <ctime>
#include <cstdlib>
#define NUM 10001
using namespace std;



int com(const void *a,const void *b)
{
return *((int*)a)-*((int*)b);//從小到大
}



int main() {
int N,X[NUM],Y[NUM];//存放x和y坐標
cin>>N;//表示士兵數
for ( int i = 0; i<N;i++){
cin>>X[i]>>Y[i];
}//輸入操作


//士兵移動x坐標或者是y坐標移動一個單位,在poj上提交可以不用這個功能;
for( int i = 0 ; i<N;i++){
int dirction =0+ rand()%(int)(1-0+1);//移動的坐標
int move = (-1)+rand()%(int)(1-(-1)+1);//移動方向

if(dirction == 0){
int x = X[i] + move;
int j = 0;
for(j = 0; j<N;j++){
if(x!=X[j]&&Y[i]!=Y[j])//有相同的坐標就不需移動
continue ;
else
break;
}
if(j == N)
X[i] = x;
}
//移動x坐標
else if(dirction == 1){
int y = Y[i] + move;
int j = 0;
for(j = 0; j<N;j++){
if(y!=Y[j] &&X[i]!=X[j])//有相同的坐標就不需移動
continue ;
else
break;
}
if(j == N)
Y[i] = y;
}//移動y坐標

}






qsort(Y, N, sizeof(int), com);
qsort(X, N, sizeof(int), com);
for(int i = 0;i<N;i++){
X[i] -=i;
}
qsort(X, N, sizeof(int), com);
int sum = 0;
for(int i = 0; i<N;i++){//移動的最小步數
sum +=abs(X[i]-X[N/2])+abs(Y[i]-Y[N/2]);
}
cout<<endl;
return 0;
}
關於用上面所討論的尋找中位數的方法可以自己試試;


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com