题目描述
有 n个坐标不同的村庄排列在一条直线上。需要重从其中选择 m 个来建邮局。每个村
庄使用离它最近的邮局。 该如何选择邮局,使得各村庄到其最近的邮局的距离总和最小。 输入格式
输入第一行两个整数 n(1≤n≤2000),m(1≤m≤min(n,1000)) 。 接下里一行输入 n个村庄的坐标 Xi(1≤Xi≤10000)。
输出格式
输出最小的距离总和。
样例输入
10 5
1 2 3 6 7 9 11 22 44 50
样例输出
9
初步分析
首先我们知道,每一个邮局都有几个管辖的村庄。每个邮局所管辖的村庄到这个邮局的距离之和一定是最短的。如果我们用dp[i][j]表示前j个村庄有i个邮局的最小距离和。那么最后一个邮局一定是管辖到了几个村庄了的。那么它的范围是多少呢?这个我们不知道,如果它管辖的范围是第k+1个到第j个。那么前面的从第一个到第k个村庄的最小距离之和一定是dp[i-1][k].(可以用反证法想想是不是这样,前面k个和后面的j-k个是属于不同的两个部分,但都必须得两个都是最小的距离之和才行)。前面k个的最小值是dp[i-1][k]那后面的j-k个呢?
我们再提出一条结论
如果第i到第j个村庄之间只有一个邮局,那么这个邮局建立在第(i+j)/2个村庄处距离和是最小的
这个其实很好想,大家画图看一下就行了,如果村庄数是偶数的话其实建立在第(i+j)/2和第(i+j)/2+1处都是一样的距离和。
所以我们用w[i][j]表示从第i个村庄到第j个村庄之间只有一个邮局时的最小距离之和。
过程分析
经过上面的分析,我想我们已经可以写下状态方程了。
dp[i][j]=min{dp[i-1][k]+w[k+1][j]}
知道四边形优化dp的都知道了这个形式是可以用四边形不等式优化的。不懂的可以先看看我的这篇博客,也是差不多类型的,不过讲的详细一点。
集合分割
所以这里就直接上代码了
注意
如果决策数组是用的s[i-1][j]=<s[i][j]<=s[i][j+1]这个不等式的话,dp的k值转化就一定得是第二维,即只能用d[i][j]里的j表示成前j个村庄而不能用i表示
#include <iostream>
#include <math.h>
using namespace std;
const int MAX_N=2020;
const int MAX_M=1010;
const int inf=0x3f3f3f3f;
int dp[MAX_M][MAX_N];
int s[MAX_M][MAX_N];
int w[MAX_N][MAX_N];
int x[MAX_N];
int n,m;
void init(){
for(int i=1;i<=n;++i){
for(int j=i;j<=n;++j){
w[i][j]=0;
if((j-i+1)%2==0){
w[i][j]=w[i][j-1]+x[j]-x[(j+i)/2];
}
else{
for(int k=i;k<=j;++k)
w[i][j]+=abs(x[k]-x[(j+i)/2]);
}
}
}
for(int i=1;i<=n;++i){
dp[1][i]=w[1][i];
s[1][i]=1;
}
}
int main() {
cin>>n>>m;
for(int i=1;i<=n;++i){
scanf("%d",&x[i]);
}
init();
//s[i-1][j]=<s[i][j]<=s[i][j+1]
for(int i=2;i<=m;++i){
s[i][n+1]=n;
for(int j=n;j>=i;--j){
dp[i][j]=inf;
int temp;
for(int k=s[i-1][j];k<=s[i][j+1];++k){
if(dp[i][j]>dp[i-1][k]+w[k+1][j]){
dp[i][j]=dp[i-1][k]+w[k+1][j];
temp=k;
}
}
s[i][j]=temp;
}
}
cout<<dp[m][n];
return 0;
}