邮局选址(四边形不等式优化)

题目描述
 有 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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值