题目: https://vjudge.net/contest/240167#problem/A
有一条公路,该公路视为一个整数轴,每个村庄的位置用一个整数坐标来标识,每个村庄都是独立的,没有重叠的。邮局建立在村庄上。现在的问题是,怎么建立邮局,从而使得每个村庄和最近邮局之间的距离总和最小, 并求出最小的距离和。
输入:
第一行包含两个整数,村庄数量 V (1<=V<=300)邮局数 P (1<P<=30).
第二行包含V个整数的递增顺序,这些V整数代表村庄的位置,且位置的取值范围是:[1, 10000].
输出:
一个整数S,它是每个村庄和它最近邮局之间的距离的总和。
Sample Input:
10 5
1 2 3 6 7 9 11 22 44 50
Sample Output:
9
题解:
1、首先把该问题进行简化,先想在V个村庄之间建立一个邮局,算每个村庄到该邮局的距离总和的最小值。
如果V是奇数,那么该邮局建立在最中间的位置时,可以保证距离总和最小。
如果V是偶数,那么该邮局建立在中间的左边或右边都一样。
因此可以建立求解在L到R之间有一个村庄时,最短距离总和的递推关系式:
W[ L ][ R ] = W[ L ][ R - 1 ] + a[ R ] - a[ ( R + 1 ) >> 1 ]
为什么会有这个递推关系式呢? 解释如下:
解释一下图片:
1~4的距离和 == 2~3的距离 + 1~4的距离 (邮局在2位置或3位置上,也就是中间位置,这样才可以保证所有村庄到邮局的距离之和最短)
1~5的距离和 == 1~4的距离和 + a[5] - a[3]
那么。。。 1~6的距离和 == 1~5的距离和加上a[6] - a[3].
则可以总结为: W[ L ][ R ] = W[ L ][ R - 1 ] + a[ R ] - a[ ( R + 1 ) >> 1 ]
然后设dp[ i ][ j ] 代表从起点到 i 点建立 j 个邮局的情况
可以递推出: dp[ i ][ j ] = min( dp[ i ][ j ], dp[ k ][ j - 1 ] + w[ k+ 1][ i ] )
意思是:设从 开始 到 k 有 j - 1 个邮局 从 k+1到 i 有1 个邮局, 通过遍历一遍 k 值,选择出当在不同的区间的时候,判断在哪个区间设置哪一个位置的邮局会使得结果最优。 举个栗子:dp[ 10 ][ 3 ] = dp[ 6 ][ 2 ] + w[ 7 ][ 10 ] 翻译:--> 从开始到第10个村庄建三个邮局,可以看作,从开始到第六个村庄建2个邮局加上7~10之间的村庄建1个邮局。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int INF = ~0u>>1;
int a[309];
int dp[309][309]; //dp[i][j] 代表从起点到i点之间 建立 j 个邮局的情况;
int w[309][309]; // w[l][r] 为 在点L与点R之间建立一个邮局的最短距离之和 ; w[l][r] = w[l][r-1] + a[r] - a[(r+l)>>1];
int n, m;
int main()
{
cin>>n>>m;
for(int i = 1; i <= n; i++) cin>>a[i]; //输入村庄的位置
memset(w, 0, sizeof(w)); // 把所有的距离置0
for(int i = 1; i <= n; i++){
for(int j = i + 1; j <= n; j++){
w[i][j] = w[i][j - 1] + a[j] - a[(i + j) >> 1]; //计算在L到R之间有一个邮局时, 所有村庄的距离最短之和
}
}
for(int i = 1; i <= n; i++){
dp[i][1] = w[1][i]; //在1-i之间建立一个邮局时 最短距离
dp[i][i] = 0;
}
for(int i = 1; i <= n; i++){ // 最后一个村庄的位置
for(int j = 2; j <= min(m, i); j++){ //min(m, j) 保证了 邮局的个数比村庄的个数少
dp[i][j] = INF;
for(int k = j; k <= i - 1; k++){ //k是为了一个一个的扩展。 意思是: 从 1 到 k 建立了 j - 1 个邮局 加
//上 从 k-1 到 i 建立一个邮局时 在k-1到i区间上的所有村庄到该邮局的距离之和的最小值
dp[i][j] = min(dp[i][j], dp[k][j - 1] + w[k + 1][i]);
//dp[i][j] = dp[k][j - 1] + w[k + 1][i]
//k起始位置应该大于邮局的数量,然后来挨个遍历递推,找最优的。
}
}
}
cout<<dp[n][m];
}