中位數及帶權中位數

信息學競賽總是時不時與數學產生微妙的關係,中位數及帶權中位數問題有時常常成爲解題的關鍵,今日有時間,所以梳理一下。

先從一到簡單的題看起:

士兵站隊問題

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

分析:這個問題我們可以把X,Y分開看,兩者互不影響。其實就是求所以橫座標的中點,也就是中位數,那麼爲什麼呢?


我們可以把所選定的位置左右的兩個點看成一對,只要所選位置在兩者之間,那麼長度恆等於兩點的線性距離和,所以我們可以根據每一對不斷縮小我們所選位置的範圍,最後如果有奇數個點,那麼就會在中間的那個點上,如果是偶數那麼在中間兩個數和他們所構成的區間,這樣想就容易發現中位數這一規律了。

[cpp] view plain copy
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<algorithm>  
  4. using namespace std;  
  5. int x[10010],y[10010];  
  6. int main()  
  7. {  
  8.     freopen("sol.in","r",stdin);  
  9.     freopen("sol.out","w",stdout);  
  10.     int n,i,sum=0,s;  
  11.     cin>>n;  
  12.     for (i=1;i<=n;i++)  
  13.       scanf("%d%d",&x[i],&y[i]);  
  14.     sort(x+1,x+n+1);  
  15.     sort(y+1,y+n+1);  
  16.     s=y[(1+n)/2];  
  17.     for (i=1;i<=n;i++)  
  18.       sum+=abs(y[i]-s);  
  19.     for (i=1;i<=n;i++)  
  20.       x[i]-=i;  
  21.     sort(x+1,x+n+1);  
  22.     s=x[(1+n)/2];  
  23.     for (i=1;i<=n;i++)  
  24.       sum+=abs(x[i]-s);  
  25.     cout<<sum<<endl;  
  26.     fclose(stdin);  
  27.     fclose(stdout);  
  28.     return 0;  
  29. }  

再看看中位數與動歸結合的應用:

[問題描述]

一些村莊被建在一條筆直的高速公路邊上。我們用一條座標軸來描述這條高速公路,每一個村莊的座標都是整數。沒有兩個村莊座標相同。兩個村莊間的距離,定義爲它們座標值差的絕對值。

人們需要在一些村莊建立郵局——當然,並不是每一個村莊都必須建立郵局。郵局必須被建在村莊裏,因此它的座標和它所在的村莊座標相同。每個村莊使用離它最近的那個郵局,建立這些郵局的原則是:所有村莊到各自所使用的郵局的距離總和最小。

你的任務是編寫一個程序,在給定了每個村莊的座標和將要建立的郵局數之後,按照上述原則,合理地選擇這些郵局的位置。

輸入文件的文件名是post.in

文件的第輸入文件中同一行相鄰兩項之間用一個或多個空格隔開。

一行是包含兩個整數:第一個整數是村莊的數目V,1〈=V〈=300,第二個整數是將建立的郵局數P,1〈=P〈=30且P〈=V。

文件的第二行按照遞增順序列出了V個整數。這V個整數分別表示了各村莊的位置座標。對於每一個位置座標X,1〈=X〈=10000。

輸出文件名是post.out

文件的第一行是一個整數S,表示你所求出的所有村莊到離它最近郵局的距離的總和。

相應地,文件的第二行按照遞增順序列出了P個整數,分別表示你所求出每個郵局的建立位置。雖然對於同一個S,可能會有多種郵局建立的方案,但只需輸出郵局位置儘量靠前的一組。

Example

Post.in

10   5

1 2 36 7 9 11 22 44 50

Post.out

9

2 7 2244 50 

這一道題是很經典的區間動態規劃題,在預處理中就用到了上述思想。

[cpp] view plain copy
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cmath>  
  4. using namespace std;  
  5. int n,m,len[320][320],f[320][320],a[320],s[320][320],m1[320][320];  
  6. void print(int x,int y)  
  7. {  
  8.     if (x==0)  
  9.      return;  
  10.     print(x-1,s[x][y]);  
  11.     printf("%d ",a[m1[s[x][y]+1][y]]);  
  12. }  
  13. int main()  
  14. {  
  15.     freopen("post.in","r",stdin);  
  16.     freopen("post.out","w",stdout);  
  17.     int i,j,k;  
  18.     scanf("%d%d",&n,&m);  
  19.     for (i=1;i<=n;i++)  
  20.       scanf("%d",&a[i]);  
  21.     for (i=1;i<=n;i++)  
  22.       for (j=i;j<=n;j++)  
  23.         {  
  24.             m1[i][j]=(i+j)/2;  
  25.             int temp1=0;  
  26.             for (k=i;k<=j;k++)  
  27.               {  
  28.                 len[i][j]+=abs(a[k]-a[m1[i][j]]);  
  29.               }  
  30.               
  31.         }  
  32.     memset(f,127,sizeof(f));  
  33.     for (i=1;i<=n;i++)  
  34.       {  
  35.         f[1][i]=len[1][i];  
  36.         s[1][i]=0;  
  37.       }  
  38.     for (i=2;i<=m;i++)  
  39.       for (j=i;j<=n;j++)  
  40.         for (k=i-1;k<=j-1;k++)  
  41.            if (f[i][j]>f[i-1][k]+len[k+1][j])  
  42.              {  
  43.                 f[i][j]=min(f[i][j],f[i-1][k]+len[k+1][j]);  
  44.                 s[i][j]=k;  
  45.              }  
  46.     cout<<f[m][n]<<endl;  
  47.     print(m,n);  
  48.     return 0;   
  49. }  
中位數解決了,那麼就來看一下帶權中位數問題,這個問題如果不知道,一定會覺得某些題十分的高大上,無從下手。例如



典型的帶權中位數問題,把平面轉成線性即可。爲何帶權中位數問題就是就權值的中位數呢,我們可以這麼想,不帶權的相當於權爲1,每個點只有一個人,那麼帶權就相當每個點有該點權值個人,這樣理解就與上面的思路神合了
ps:證明過程
若最優點在T
則有:
∑{D*DIST(I,T)}(I<>T)<=∑{D*DIST(I,T+1)}(I<>T+1)
將此式化爲:
∑{D[L]}*DIST(L,T)}+∑{D[R]*DIST(R,T)}+D[T+1]*DIST(T+1,T)
<=∑{D[L]}*DIST(L,T+1)}+∑{D[R]*DIST(R,T+1)}+D[T]*DIST(T,T+1) (L<T&R>T+1)
即:
∑{D[L]*DIST(L,T+1)}-∑{D[L]*DIST(L,T)}(L<T)+D[T]*(DIST(T,T+1))>=∑{D[R]*DIST(R,T)}-∑(D[R]*DIST(R,T+1))(R>T+1)+D[T+1]*(DIST(T,T+1))進一步化簡爲:
∑{D[L]*(DIST(L,T)-DIST[L,T+1])}(L<=T)<=∑{D[R]*(DIST(R,T+1)-DIST(R,T))}(R>=T+1)∵DIST(L,T)-DIST(L,T+1)=DIST(T,T+1)
DIST(R,T+1)-DIST(R,T)=DIST(T+1,T)
OBVIOUSLY : DIST(T,T+1)=DIST(T+1,T)
因此:
∑D[L](L<=T)>=∑(D[R])(R>=T+1)
即:∑D[L](L<T)+D[T]>=∑(D[R])(R>T)
因此我們發現,若T是最優點,則必有其左邊的權值和加上D[T]後大於右邊的權值和
而類似的,我們可以證明其右邊的權值和加上D[T]後大於左邊的權值和
因此我們要找的點也就是滿足以上條件的點。
注意到此時我們的選擇已經和具體的位置(座標)沒有關係了,而成爲主要考慮因素的僅僅是各點上的權值。
因爲左邊的權值和數+D[T]>=右邊的權值和,那麼:
LEFTSUM+D[T]>=RIGHTSUM=SUMALL-(LEFTSUM+D[T])
=>2*(LEFTSUM+D[T])>=SUMALL
=>2*RIGHTSUM<=SUMALL
同理可得:
RIGHTSUM+D[T]>=LEFTSUM=SUMALL-(RIGHTSUM+D[T])
=>2*(RIGHTSUM+D[T])>=SUMALL
=>2*LEFTSUM<=SUMALL
此時我們發現:
2*LEFTSUM<=SUMALL 而 2*(LEFTSUM+D[T])>=SUMALL
也即是說當前的位置T上的數包含了第[(SUMALL)/2]個數,由開篇的簡述可知,這第[(SUMALL)/2]個數,就是這個序列中的帶權中位數。所以這一類問題,實質上就是帶權中位數問題。
[cpp] view plain copy
  1. #include<iostream>  
  2. #include<cstdio>  
  3. #include<cstring>  
  4. #include<cmath>  
  5. #include<algorithm>  
  6. using namespace std;  
  7. struct data  
  8. {  
  9.  int x,y,w;  
  10. };data num[50003];  
  11. int n,i,j,k;  
  12. double sum,ans,xx,yy;  
  13. int xl,yl;  
  14. int cmp(data a,data b)  
  15. {  
  16.     return a.x<b.x;  
  17. }  
  18. int cmp1(data a,data b)  
  19. {  
  20.     return a.y<b.y;  
  21. }  
  22. int main()  
  23. {  
  24.     freopen("ball.in","r",stdin);  
  25.     freopen("ball.out","w",stdout);  
  26.     scanf("%d",&n);  
  27.     sum=ans=xx=yy=0;  
  28.     for (i=1;i<=n;i++)  
  29.      {  
  30.      scanf("%d",&num[i].w);  
  31.      sum+=num[i].w;  
  32.      }  
  33.     for (i=1;i<=n;i++)  
  34.      scanf("%d%d",&num[i].x,&num[i].y);  
  35.     sort(num+1,num+n+1,cmp);  
  36.     double mid=sum/2;  
  37.     for (i=1;i<=n;i++)  
  38.      {  
  39.         xx+=num[i].w;  
  40.         if (xx>mid)  
  41.          {  
  42.             xl=num[i].x;  
  43.             break;  
  44.          }  
  45.      }  
  46.     for (i=1;i<=n;i++)  
  47.      ans+=num[i].w*(abs(num[i].x-xl));  
  48.     sort(num+1,num+n+1,cmp1);  
  49.     for (i=1;i<=n;i++)  
  50.      {  
  51.         yy+=num[i].w;  
  52.         if (yy>mid)  
  53.          {  
  54.             yl=num[i].y;  
  55.             break;  
  56.          }  
  57.      }  
  58.     for (i=1;i<=n;i++)  
  59.      ans+=num[i].w*(abs(num[i].y-yl));  
  60.     printf("%0.2lf",ans);  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章