描述
一些村庄建在一条笔直的高速公路边上,我们用一条坐标轴来描述这条公路,每个村庄的坐标都是整数,没有两个村庄的坐标相同。两个村庄的距离定义为坐标之差的绝对值。我们需要在某些村庄建立邮局。使每个村庄使用与它距离最近的邮局,建立邮局的原则是:所有村庄到各自使用的邮局的距离总和最小。
数据规模:1<=村庄数<=300, 1<=邮局数<=30, 1<=村庄坐标<=10000
格式
输入格式
2行
第一行:n m {表示有n个村庄,建立m个邮局}
第二行:a1 a2 a3 .. an {表示n个村庄的坐标}
输出格式
1行
第一行:l {l表示最小距离总和}
样例1
样例输入1
10 5 1 2 3 6 7 9 11 22 44 50
样例输出1
9
题目分析
首先,这是一道区间的动态规划,就像矩阵连乘那样,d[ i ][ j ]表示 i 到 j的区间上的最优矩阵划分。
影响子问题的因素至少有两个,一个是村庄的区间,另一个是邮局数量。
故动态规划的子问题是d[ i ][ j ]表示的是: 在前 j 个村庄建立 i 个邮局的距离的最小值。
状态转移的思想是: 想一下,当只有一个邮局的时候,我们当然在中间的那个村庄去建一个邮局。(要注意的是:这个中间值要分奇偶数)
状态转移方程: d[ i ][ j ] = MIN{ d[ i-1 ][ k ] + w[ k+1 ][ j ] } ; 其中 i-1=<k<j, w[ k+1 ][ j ]表示的是在k+1到j的村庄建立一个邮局的最小花费( w[ k+1 ][ j ] = ∑ | a[L] - a[ mid ] | (k+1=<L<=j) && mid =(k+1+j)/2或mid=(k+2+j)/2 )
算法复杂度分析: 上面子问题共n*p个,每次转移最大为O(n),所以总的复杂度是O( n^2*p )。我在POJ上提交的时候,用了35Ms的时间。
下面简单地说下优化,用的是四边形不等式。 这次在POJ上提交用了0Ms
我就不证明了,我只是说下思路,首先要证明的是w[i][j]要满足四边形不等式,然后用数学归纳法去证明d[i][j]也是满足四边形不等式的,最后再用s[i][j]表示的是记录d[i][j]的最优分割位置,然后证明就出来了。
最终我们得到的决策是:s[ i-1 ][ j ]=<s[ i ][ j ] <= s[ i ][ j+1 ]
附上跑了0MM的代码:
最后说下,代码比较乱的部分是w[ i ][ j ]的计算,我用的技巧可以在O(1)时间内计算一个状态。
/*
邮局问题
d[ i, j ]表示前j个村庄建立i个邮局所需要的最少总路程
d[ i, j ] = min{ d[ i-1, k ] + w[ k+1, j] } ( i-1=< k < j )
*/
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 300 + 5;
int d[ maxn ][ maxn ];
int a[ maxn ];
int sum[ maxn ];
int s[ maxn ][ maxn ];
const int INF = 1<<20;
void dp( int n, int p );
int min1( int a, int b )
{
return a < b ? a : b;
}
int main()
{
int v, p;
scanf( "%d%d", &v, &p );
for( int i=0; i<v; i++ )
{
scanf( "%d", &a[ i ] );
if( i != 0 )
sum[ i ] = sum[ i-1 ] + a[ i ];
else
sum[ i ] = a[ i ];
}
dp( v, p );
return 0;
}
//dp
void dp( int n, int p )
{
memset( d, 0, sizeof( d ) );
int i, j, k;
int lowerMid, upperMid;
//initilize d[ i, j ]表示前j个村庄建立i个邮局所需要的最少总路程
for( i=0; i<n; i++ )
{
lowerMid = i/2;
upperMid = (i+1)/2;
int tmp = min1( sum[ i ] - 2*sum[ lowerMid ] + a[ lowerMid ], sum[ i ] - 2*sum[ upperMid ] + 2*a[ upperMid ] );
d[ 0 ][ i ] = tmp;
s[ 0 ][ i ] = 0;
d[ i ][ i ] = 0;
s[ i ][ i ] = i - 1;
d[ i ][ n ] = n-2;
}
for( i=1; i<p; i++ )
{
for( j=n-1; j>i; j-- )
{
d[ i ][ j ] = INF;
//优化在这里
int end;
if( j == n-1 )
end = n-2;
else
end = s[i][j+1];
for( k=s[ i-1 ][ j ]; k<=end; k++ )
{
lowerMid = (k+1+j) / 2;
upperMid = (k+2+j) / 2;
//注意: 计算lowerMid 与 upperMid两个数的tmp是不同的!!! 1个半小时的调试
int tmp = min1( sum[ j ] + sum[ k ] - 2*sum[ lowerMid ] + a[ lowerMid ], sum[ j ] + sum[ k ] - 2*sum[ upperMid ] + 2*a[ upperMid ] );
if( d[ i ][ j ] > d[ i-1 ][ k ] + tmp )
{
d[ i ][ j ] = d[ i-1 ][ k ] + tmp;
s[ i ][ j ] = k;
}
}
}
}
printf( "%d\n", d[ p-1 ][ n-1 ] );
}
本文详细介绍了如何使用动态规划解决邮局问题,通过分析输入格式和输出格式,逐步阐述了状态转移方程和算法复杂度分析。此外,文章还提供了一段经过优化的代码实现,最终达到在极短时间内解决问题的目的。
721

被折叠的 条评论
为什么被折叠?



