邮局问题_区间DP

题目: 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 ]

扫描二维码关注公众号,回复: 2433525 查看本文章

为什么会有这个递推关系式呢? 解释如下:

解释一下图片:

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];
}

猜你喜欢

转载自blog.csdn.net/ltrbless/article/details/81240158