IOI 2000 邮局问题: 动态规划

题目描述

高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。

邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。

你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。

输入输出格式

输入格式:

第一行包含两个整数:第一个是村庄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];
}

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值