题目描述
高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。
邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。
你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。
输入输出格式
输入格式:
第一行包含两个整数:第一个是村庄VV的数量,第二个是邮局的数量P,1≤P≤300,P≤V≤3000.
第二行包含V个整数。这些整数是村庄的位置。对于每个位置X,认为 ≤X≤10000。
输出格式:
第一行包含一个整数S,它是每个村庄与其最近的邮局之间的所有距离的总和。
输入输出样例
输入样例#1: 复制
10 5
1 2 3 6 7 9 11 22 44 50
输出样例#1: 复制
9
说明
对于40%的数据,V≤300
解题思路如下:
如果是n 个 村庄 建立 1个邮局,这样问题就明朗了许多,
我们先考虑建立一个邮局的情况,然后再解决本题。
假设有 四个村庄 a=1, b=2, c=3, d=4 或者 奇数个 5 个村庄 a=1, b=2, c=3, d=4,e=5, 我们不难得出 在中间位置建立邮局 得到的整体距离和最小, 这个结论应该不用算,就能想明白。基于这样的先验知识,我们可以继续思考,如果
从第 i 个 村庄 到 第 j 个村庄 建立一个邮局的最小距离和为 dis[i][j], 那么我们从动态规划的角度想, 从第i 个村庄到 第 j-1 个村庄的最小距离 dis[i][j-1] 与 dis[i][j] 差多少呢, 我们想到, 这两种计算情况,每个村庄的距离都要与村庄的中位数做差值得到,而且这两种情况的中位数是同一个 :都是以 c 村庄建立,那么距离总和差的是什么呢?
dis4=(c-a)+(c-b)+(d-c)
dis5=(c-a)+(c-b)+(d-c) +(e-c)
差的就是多的一个村庄 e 与中间的村庄 c 的距离
所以 我们得出转移方程为: dis[i][j]=dis[i][j-1]+ arr[j]-arr[(i+j)/2];
至此,我们解决了在多个村庄建立一个邮局的问题, 然后我们考虑建立多个邮局的情况
假设我们要在 n 个村庄 建立 m 个邮局 ,我们可以分两部思考
(1) 前 k 个村庄建立 m-1 个邮局
(2) 后 (n-k) 个村庄建立 1个 邮局 (是不是很熟悉?)
我们定义: 前 i 个村庄 建立 j 个邮局的的最小距离为 dp[i][j]
dp[i][j]=Math.min( dp[i][j[] , dp[k][j-1] + dis[k+1][i] )
解释:
前 i 个村庄 建立 j 个邮局的的最小距离 = 以下两种情况的最小值
前 i 个村庄 建立 j 个邮局的的最小距离 / 前k 个村庄建立 j-1 个邮局,[k+1, i] 建立一个邮局
这样我们就得到了本题需要的 动态规划的 状态转移方程
下面我们考虑代码
import java.util.*;
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int n=in.nextInt();
int m=in.nextInt();
in.nextLine(); // 下一行
int[] arr=new int[n];
for (int i = 0; i < n; i++) {
arr[i]=in.nextInt();
}
System.out.println(getSolve(arr,m));
}
private static int getSolve(int[] arr, int m) {
int n=arr.length; // 村庄个数
int[][] dp=new int[n+1][m+1];
// dp[i][j] 代表的是 前 i个村庄建立 j 个邮局的 最短路径和
int[][] dis=new int[n+1][n+1];
// dis[i][j] 代表的是 第 i 个村庄 与 第 j 个村庄之间 建立 一个邮局的 最小路径和,
//需要注意 我们都是从 1 开始的
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
dis[i][j]=dis[i][j-1] + arr[j-1]-arr[(i-1+j-1)/2];
//此处注意 我们的 dis 从 1 开始,但是 原数组还是从0 开始,也就是后半部分
// 计算 dis[i][j] 时, 我们 dis[i][j-1] 已知, 无论 j-1 为奇数还是偶数,
// dis[i][j] 都等于 dis[i][j-1] + j 距离 i, j 中点的距离
}
}
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
dp[i][j]=Integer.MAX_VALUE; // 初始化为最大
}
}
for(int i=1;i<=n;i++){
dp[i][1]=dis[1][i]; // 当只有一个邮局时候, 初始操作
}
for(int i=1;i<=n;i++){ // 前 i 个村庄
for(int j=2;j<=m;j++){ // 前 i 个村庄 建立 j 个邮局
for(int k=j-1;k<i;k++){ // 前k 个村建立j-1 个邮局
dp[i][j]=Math.min(dp[i][j],dp[k][j-1]+dis[k+1][i]);
}
}
}
return dp[n][m];
}