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

posted @ 2018-03-22 16:24  cjfdf  阅读(204)  评论(0编辑  收藏  举报