POJ1160 Post Office题解

该博客主要解析POJ1160问题,涉及在已知别墅位置的情况下,如何通过动态规划确定最佳超市位置以使所有别墅到最近超市距离总和最小。博主通过实例分析得出结论,并给出状态转移方程,指出预处理dis[i][j]的重要性以降低时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

POJ1160 Post Office

传送门

题目描述

在笔直的大道上建有 n n n个大别墅,两个大别墅之间的距离是他们的横坐标之差的绝对值,保证大别墅之间的距离只能是整数,且没有别墅的位置是相同的。
现在需要建立 m m m座大超市 ( m ≤ n ) (m \leq n) (mn),大超市只能建立在别墅所在的位置,现在需要你写个程序,给定了所有别墅的位置和大超市的数目,计算出每个别墅离最近的大超市的距离总和的最小值。

输入格式

第一行包括两个整数 n n n m m m
第二行包括n个整数,代表别墅的位置,以升序的形式列出。对于每一个整数 x x x.

样例输入

10 5
1 2 4 6 7 10 11 25 44 70

样例输出

11

数据范围

1 < = n < = 300 1 <= n <= 300 1<=n<=300
1 < = m < = 30 1 <= m <= 30 1<=m<=30
m < = n m <= n m<=n
1 < = X < = 10000 1 <= X <= 10000 1<=X<=10000


我们先考虑这样一个问题:三个别墅分别在 ( 1 , 7 , 20 ) (1, 7, 20) (1,7,20)中选一个超市,最小的距离和是多少。
Alt
答案是在中间那个超市,因为 ( 20 − 1 + 1 ) ÷ 2 = 10 (20-1+1) ÷ 2 = 10 201+1÷2=10,离 10 10 10最近的超市是7,所以我们选中间这个别墅做超市,那么再来有5个别墅的情况:
在这里插入图片描述
答案还是离 ( 20 − 1 + 1 ) ÷ 2 = 10 (20-1+1) ÷ 2=10 201+1÷2=10的最近的别墅7,所以我们得出一个结论: 只有超市位于中间位置,距离和才最小

注意这个位置不是中间的那个别墅,而是离中间的那个地方最近的别墅。

d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑前 i i i个别墅建 j j j个超市的最小代价。

那么 d p [ i ] [ j ] dp[i][j] dp[i][j]可以从哪个地方转移过来呢?

d p [ k ] [ j − 1 ] dp[k][j - 1] dp[k][j1],因为我们可以把 k k k前面的别墅分成一块 k k k i i i的别墅另外分成一块,这一块只建一个超市。

所以状态转移方程是: d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] ,    d p [ k ] [ j − 1 ] + d i s [ k + 1 ] [ i ] ) dp[i][j]=min(dp[i][j],\text{ }\text{ }dp[k][j-1]+dis[k+1][i]) dp[i][j]=min(dp[i][j],  dp[k][j1]+dis[k+1][i])
注: d i s [ i ] [ j ] dis[i][j] dis[i][j]表示第i个别墅到第j个别墅只建一个超市的最小代价

这个 d i s [ i ] [ j ] dis[i][j] dis[i][j]是需要预处理的,因为如果不预处理,每次就需要枚举最优决策点,时间复杂度为 O ( P 2 V 2 ) O(P^2 V^2) O(P2V2)是会超时的,但是我们可以用 V 2 V^2 V2预处理 d i s [ i ] [ j ] dis[i][j] dis[i][j],那么时间复杂度就降成 O ( P 2 V ) O(P^2V) O(P2V)就不会超时了。

那现在来看看这个 d i s [ i ] [ j ] dis[i][j] dis[i][j]怎么算:

我们先去掉 i i i~ j j j区间的最后一个,即只剩下了 i i i ~ j − 1 j-1 j1,这个区间只建一个超市的最小代价是 d i s [ i ] [ j − 1 ] dis[i][j-1] dis[i][j1],最后加上新加的这个节点的与最优决策点的差就行了。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 305
#define INF 0x3f3f3f3f
int V, P;
int x[MAXN], dp[MAXN][MAXN], dis[MAXN][MAXN];
int main()
{
	scanf("%d %d", &V, &P);
	memset(dp, INF, sizeof(dp)); 
	for(int i = 1; i <= V; i++)
		scanf("%d", &x[i]);
	for(int i = 1; i <= V; i++)
		dis[i][i] = 0;
    for(int i = 1; i <= V; i++)
    	for(int j = i + 1; j <= V; j++)
			dis[i][j] = dis[i][j - 1] + x[j] - x[(i + j) / 2];
	for(int i = 1; i <= V; i++)
		dp[i][1] = dis[1][i];
	for(int j = 1; j <= P; j++)
	    for(int i = j + 1; i <= V; i++)
	        for(int k = 1; k <= i; k++)
	            dp[i][j] = min(dp[i][j], dp[k][j - 1] + dis[k + 1][i]);
	cout << dp[V][P] << endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值