[IOI2000][POJ1160]Post office

题面在这里

题意

一条路上有\(n\)个村庄,坐标分别为\(x[i]\),你需要在村庄上建设\(m\)个邮局,使得
每个村庄和最近的邮局之间的所有距离总和最小,求这个最小值。

数据范围

\(1\le n\le300,1\le m\le30\)

sol

=山区建小学。
\(f[i][j]\)表示把\(i\)个邮局建到前\(j\)个村庄时的最小距离总和,转移方程
\[f[i][j]=\min_{k=i-1}^{j}{(f[i-1][k]+d[k+1][j])}\]
其中\(d[l][r]\)表示在区间\([l,r]\)内修建一个邮局,所能得到的最小距离总和,
可以根据中位数在\(O(n^2)\)的时间内预处理出来,
总复杂度为\(O(n^3)\)

四边形不等式优化

首先需要证明\(d[i][j]\)满足四边形不等式(区间包含关系单调显然),
对于\(i\le i'\le j\le j'\),我们设\(y=mid(i,j'),z=mid(i',j)\)
\(y\le z\)时(由于对称性,当\(y>z\)时可同法证)有
\[d[i][j]+d[i'][j']\le \sum_{k=i}^{j}{|x[k]-x[y]|}+\sum_{k=i'}^{j'}{|x[k]-x[z]|}\]
\[=\sum_{k=i}^{j}{|x[k]-x[y]|}+\sum_{k=j+1}^{j'}{|x[k]-x[z]|}+\sum_{k=i'}^{j}{|x[k]-x[z]|}\]
\[\le \sum_{k=i}^{j}{|x[k]-x[y]|}+\sum_{k=j+1}^{j'}{|x[k]-x[y]|}+\sum_{k=i'}^{j}{|x[k]-x[z]|}\]
\[=\sum_{k=i}^{j'}{|x[k]-x[y]|}+\sum_{k=i'}^{j}{|x[k]-x[z]|}\]
\[=d[i][j']+d[i'][j]\]

证明\(f[i][j]\)满足四边形不等式时,用合并石子类似的方法数学归纳+分类讨论即可

代码

注意一些边界和细节问题

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cstring>
#include<complex>
#include<vector>
#include<cstdio>
#include<string>
#include<bitset>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define mp make_pair
#define pb push_back
#define RG register
#define il inline
using namespace std;
typedef unsigned long long ull;
typedef vector<int>VI;
typedef long long ll;
typedef double dd;
const dd eps=1e-10;
const int mod=1e8;
const int N=310;
il ll read(){
    RG ll data=0,w=1;RG char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
    return data*w;
}

il void file(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
}

int n,m,x[N],sx[N],s[N][N],d[N][N],f[N][N];

int main()
{
    n=read();m=read();
    for(RG int i=1;i<=n;i++)x[i]=read(),sx[i]=x[i]+sx[i-1];
    for(RG int l=1;l<=n;l++)
        for(RG int r=l;r<=n;r++){
            RG int mid=(l+r)>>1;
            d[l][r]=x[mid]*(mid-l)-(sx[mid-1]-sx[l-1])+(sx[r]-sx[mid])-x[mid]*(r-mid);
        }

    for(RG int i=1;i<=n;i++)s[0][i]=0,s[i][n+1]=n-1;
    
    memset(f,63,sizeof(f));
    f[0][0]=0;  
    for(RG int i=1;i<=m;i++)
        for(RG int j=n;j>=i;j--)
            for(RG int k=s[i-1][j];k<=s[i][j+1];k++)
                if(f[i][j]>=f[i-1][k]+d[k+1][j]){
                    f[i][j]=f[i-1][k]+d[k+1][j];s[i][j]=k;
                }

    printf("%d\n",f[m][n]);
    
    return 0;
}

转载于:https://www.cnblogs.com/cjfdf/p/8624626.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值