Post offices will be built in some, but not necessarily all of the villages. A village and the post office in it have the same position. For building the post offices, their positions should be chosen so that the total sum of all distances between each village and its nearest post office is minimum.
You are to write a program which, given the positions of the villages and the number of post offices, computes the least possible sum of all distances between each village and its nearest post office.
Time Limit:10000MS Memory Limit:65536K
Description
[问题描述]
一些村庄被建在一条笔直的高速公路边上,我们用一条坐标来描述这条高速,每一村庄的坐标都是整数,没有两个村庄坐标相同,两个村庄间的距离,定义为它们坐标值差的绝对值。
我们需要在一些村庄建立邮局,当然,并不是每一个村庄都必须建立邮局。邮局必须被建在村庄里,因此它的坐标和它所在的村庄坐标相同。每个村庄使用离它最近的那个邮局,建立这些邮局的原则是:所有村庄到各自使用的邮局的距离总和最小。
你的任务是编写一程序,在给定了每个村庄的坐标和将要建立的邮局数之后,按照上述原则,合理地选择这些邮局的位置。
Input
文件的第一行包含两个整数:第一个整数是村庄的数目V,1 < V < 300;第二个整数是将建立的邮局数P,1 < P < 30 且 P < V。
文件的第二行按照递增顺序列出了V个整数。这V个整数分别表示了各村庄的位置坐标。对于每一个位置坐标X,1 < X < 10000。
Output
一行,一个整数S,表示你所求出的所有村庄到离它最近邮局的距离总和。
Sample Input
10 5
1 2 3 6 7 9 11 22 44 50
Sample Output
9
朴素的区间dp就可以解决这道题了,容易看出dp方程为:
dp[i][j]=min(dp[i][j],dp[k][j-1]+w[k+1][i]);
其中dp[i][j]表示前i个村庄建j个邮局的最小距离和,k枚举1到i的所有村庄;w[k+1][i]表示第k+1个村庄到第i个村庄建一个邮局的最小距离和,有一个显然的性质:在某一段区间上建一个邮局,最小距离和为在其中点村庄上建。因此n^3可以预处理出w数组,dp本身三重循环也是n^3,总时间复杂度为O(n^3)。
下面我们开始说一个更优的dp:
用s[i][j]表示dp[i][j]的决策变量,即在dp方程dp[i][j]=min(dp[i][j],dp[k][j-1]+w[k+1][i])中,dp[s[i][j]][j-1]+w[s[i][j]+1][i]最优;我们可以发现对于任意区间[i,j]含于[i',j'](即区间[i',j']包含[i,j]),总有w[i][j]<w[i'][j'],我们称它符合区间单调性,在满足w的区间单调性情况下,如果w也满足四边形不等式,那么决策变量s也符合区间单调性(详细证明大家另找资料)。
整理一下,如果一个状态转移方程可以写成类似于dp[i][j]=dp[s[i][j]][j-1]+f(s[i][j])的形式,那么只要f满足区间单调性与四边形不等式,决策变量s也符合区间单调性。四边形不等式就是通过证明f(i,j)+f(i',j')<=f(i',j)+f(i,j'),i<=i'<=j<=j',从而证明决策变量满足区间单调性。
那么,我们在第三重循环枚举k时就不需要从1枚举到i了,由区间单调性知:s[i][j]一定在s[i-1][j]到s[i][j+1]、或s[i][j-1]到s[i+1][j]之间,我们只需要在这之间枚举,更新s[i][j]和dp[i][j]即可。
但是对于w的预处理我们也要优化,否则时间复杂度还是O(n^3),w[i][j]是在第i到第j个村庄建一个邮局的最小距离和,前面提到在中点村庄上建,可以推出如下式子w[i][j]=w[i][j-1]+abs(x[i]-x[(i+j)/2].
考虑两种情况,一种是(i+j-1) mod 2==0,这时(i+j)/2==(i+j-1)/2,故建邮局的村庄不变,只要把新村庄到原邮局的距离加进w[i][j]即可;
对于(i+j-1) mod 2==1时,新邮局要在原邮局位置往右挪一位,举个例子:
1 2 3 4 5 6 7
i L N j
(其中L表示原邮局,N表示新邮局)
我们发现移动邮局后,对于村庄1、2、3,距离都增加了x[4]-x[3],而对于区间4、5、6,距离都减少了x[4]-x[3],所以对于前i到j-1个邮局,距离和是不变的,只要把新村庄到新邮局的距离加进w[i][j]即可。
代码如下:
#include<cstdio>
#include<cstring>
using namespace std;
int f[301][31];
int w[301][301];
int s[301][301];
int x[301];
int v,p;
int abs(int x)
{
if (x<0) return (-x);
return x;
}
int main()
{
scanf("%d%d",&v,&p);
for (int i=1;i<=v;i++)
scanf("%d",&x[i]);
for (int i=1;i<=v;i++)
for (int j=i+1;j<=v;j++)
{
int mid=(i+j)/2;
w[i][j]+=w[i][j-1]+abs(x[j]-x[mid]); //预处理w数组
}
int INF=2147483647/3;
for (int i=1;i<=v;i++) //初始化f[i][1]与s[i][1]
{
f[i][1]=w[1][i];
s[i][1]=1;
}
for (int j=2;j<=p;j++) //因为f[i][1]已初始化,j从2开始枚举
{
s[v+1][j]=v; //限定最大区间
for (int i=v;i>j;i--) //注意要倒着枚举
{
f[i][j]=INF;
for (int k=s[i][j-1];k<=s[i+1][j];k++)
if (f[i][j]>f[k][j-1]+w[k+1][i])
{
f[i][j]=f[k][j-1]+w[k+1][i];
s[i][j]=k;
}
}
}
printf("%d",f[v][p]);
}