Sample Input
6 356121920270 0
Sample Output
Chain 1Depot 1 at restaurant 2 serves restaurants 1 to 3Depot 2 at restaurant 4 serves restaurants 4 to 5Depot 3 at restaurant 6 serves restaurant 6Total distance sum = 8
Source
Southwestern European Regional Contest 1998
【題目大意】
一條公路上有n個旅館,選出其中k個設定倉庫,一個倉庫可服務若干個旅館,一個旅館只需一個倉庫服務。問在哪幾個旅館設定倉庫,每個倉庫服務哪些旅館,可使得旅館到倉庫的總距離最小,並求出總距離(長理只要求求最後一步)。
【資料範圍】
1 <= n <= 200, 1 <= k <= 30, k <= n
【解題思想】
1、此題屬於明顯動態規劃題,關鍵點是找狀態轉移方程。
2、可以用sum[i][j]表示前i個旅館,設定j個倉庫得到的距離和最小值,那麼sum[n][k]即為所求。
3、找sum[i][j]的子結構,假設前j-1個倉庫服務第1個到第k個旅館,則最後一個倉庫服務第k+1個到第i個旅館。
4、可以用one[i][j]表示一個倉庫服務第i個到第j個旅館,到這個倉庫距離和的最小值。
5、則得到狀態轉移方程:sum[i][j]=min(sum[k][j-1]+one[k+1][i]) (j-1<=k<=i-1,min表示所有k取值得到的值中的最小值)。
6、問題轉換為了求one[i][j],即在第i到第j家旅館中設定一個倉庫的總距離。
7、假設i到j共有奇數家旅館,我們嘗試將倉庫放置在中間旅館,即旅館(i+j)/2,假設將倉庫左移距離x,則右半邊所有旅館到倉庫距離均加x,而只有部分左半邊旅館距離減少了x,剩下的減少均小於x,甚至不減少。因此可以得到,將倉庫從中間位置左移到任何位置總距離都會增加,右移同理,因此倉庫放到旅館(i+j)/2最合適。
8、假設i到j共有偶數家旅館,容易得到將倉庫放到(i+j-1)/2和(i+j+1)/2得到的總距離相等(對稱性),若將倉庫放到(i+j-1)/2,並左移,則用7相似的想法可得知總距離增大,右移情況同理,由此得知倉庫放到(i+j-1)/2這個位置即可滿足總距離最小。
9、由7、8得到one[i][j]實際上時將倉庫放到(i+j)/2取整位置可得到最小的總距離。
10、資料範圍較小,我們可以計算出一切one[i][j]的組合。
11、由於poj還要求輸出在哪幾個旅館設定倉庫,每個倉庫服務哪些旅館,因此還需要儲存動態規劃路徑。
12、可用at[i][j],from[i][j],to[i][j]分別表示sum[i][j]得到最小值時最後一個倉庫的位置、服務的起始位置和服務的終止位置。
13、通過遞迴輸出結果。
#include<stdio.h>#include<algorithm>#include<string.h>using namespace std;const int INF=100000000;int r[300],sum[300][40],one[300][300];int from[300][40],to[300][40],at[300][40];int output(int i,int j){ if(j<=0||i<=0)return 1; int num=output(from[i][j]-1,j-1); printf("Depot %d at restaurant %d serves ",num,at[i][j]); if(from[i][j]==to[i][j])printf("restaurant %d\n",from[i][j]); else printf("restaurants %d to %d\n",from[i][j],to[i][j]); return num+1;} int main(){ int n,K,i,j,k,middle; int iCase=0; while(scanf("%d%d",&n,&K)!=EOF) { iCase++; if(n==0&&K==0)break; for(i=1;i<=n;i++) scanf("%d",&r[i]); memset(one,0,sizeof(one)); memset(sum,0,sizeof(sum)); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { middle=(i+j)/2; //for(k=i;k<middle;k++)one[i][j]+=r[middle]-r[k]; //for(k=middle+1;k<=j;k++)one[i][j]+=r[k]-r[middle]; for(k=i;k<=j;k++)one[i][j]+=abs(r[middle]-r[k]); } } for(i=1;i<=n;i++) sum[i][0]=INF; for(i=1;i<=n;i++) { for(j=1;j<=i&&j<=K;j++) { sum[i][j]=INF; for(k=j-1;k<=i-1;k++) { int tmp=sum[k][j-1]+one[k+1][i]; if(tmp<sum[i][j]) { sum[i][j]=tmp; from[i][j]=k+1; to[i][j]=i; at[i][j]=(k+1+i)/2; } } } } printf("Chain %d\n",iCase); output(n,K); printf("Total distance sum = %d\n\n",sum[n][K]); } return 0;}