[区间DP]POJ 1160:Post Office 邮局选址简单证明+详解

题目大意

题目链接
给出一条直线上的n个坐标表示村庄的位置,然后要在上面建p个邮局,村民优先选择去近的邮局,问所有村庄去邮局的最小距离和是多少?

思路分析

首先暴搜显然很叼,每个村庄两种状态要么建一个邮局,要么不。 2 300 2^{300} 2300搜索量,不过搜索可不止是暴搜,可以剪枝吗我不会 ,考虑一下记忆话搜索,这其实是一个有最优子结构的问题。

首先我们考虑一下,如果有 n 1 , n 2 , n 3 , n 4 n1,n2,n3,n4 n1,n2,n3,n4四个点,只能有一个邮局,那么应该建在哪里?

  • 考虑建在边缘节点 n 1 n1 n1,此时总距离为 d 1 = n 2 − n 1 + n 3 − n 1 + n 4 − n 1 = ( n 2 + n 3 + n 4 ) − 3 ∗ n 1 d1=n2-n1+n3-n1+n4-n1=(n2+n3+n4)-3*n1 d1=n2n1+n3n1+n4n1=(n2+n3+n4)3n1
  • 考虑建在边缘节点 n 4 n4 n4,此时距离为 d 2 = n 4 − n 1 + n 4 − n 2 + n 4 − n 3 = 3 ∗ n 4 − ( n 1 + n 2 + n 3 ) d2=n4-n1+n4-n2+n4-n3=3*n4-(n1+n2+n3) d2=n4n1+n4n2+n4n3=3n4(n1+n2+n3)
  • 考虑建在中点 n 2 n2 n2,此时距离为 n 2 − n 1 + n 3 − n 2 + n 4 − n 2 = n 3 + n 4 − n 1 − n 2 < n 3 + n 4 − 2 ∗ a 1 < n 2 + n 3 + n 4 − 3 ∗ n 1 < d 1 n2-n1+n3-n2+n4-n2=n3+n4-n1-n2<n3+n4-2*a1<n2+n3+n4-3*n1<d1 n2n1+n3n2+n4n2=n3+n4n1n2<n3+n42a1<n2+n3+n43n1<d1
  • 考虑建在中点 n 3 n3 n3,此时的距离为 d 4 = n 3 − n 1 + n 3 − n 2 + n 4 − n 3 = n 3 + n 4 − n 1 − n 2 < 2 ∗ n 4 − n 1 − n 2 < 3 ∗ n 4 − n 1 − n 2 − n 3 < d 2 d4=n3-n1+n3-n2+n4-n3=n3+n4-n1-n2<2*n4-n1-n2<3*n4-n1-n2-n3<d2 d4=n3n1+n3n2+n4n3=n3+n4n1n2<2n4n1n2<3n4n1n2n3<d2

显然在任何一个中间点建立邮局效果都一样,都小于在端点处建立邮局。还注意这个形式哈,如果是5个节点,那么最小距离是这样的: n 5 + n 4 − n 2 − n 1 n5+n4-n2-n1 n5+n4n2n1,有没有感受到什么,从n3分开,区间右边坐标减去左边。偶数是从中点分开,区间右边坐标减左边(等下有用)。总结一下:

  • 对于奇数个节点(k个),如果只有一个邮局,那么我们将他建立在中点,总距离为 n k + n k − 1 + . . . + n ( k + 1 ) / 2 + 1 − n ( k + 1 ) / 2 − 1 − . . . n 1 n_k+n_{k-1}+...+n_{(k+1)/2+1}-n_{(k+1)/2-1}-...n_1 nk+nk1+...+n(k+1)/2+1n(k+1)/21...n1
  • 对于偶数个节点(k个),考虑只有一个邮局,同样再中点,总距离为 n k + n k − 1 + . . . + n k / 2 + 1 − n k / 2 − . . . − n 1 n_k+n_{k-1}+...+n_{k/2+1}-n_{k/2}-...-n_1 nk+nk1+...+nk/2+1nk/2...n1

然后我们看看他的最优子结构是什么样的。对N个村庄建P个邮局,相当于每个邮局均有一个作用范围,该邮局位于其作用范围的中间位置,就是要找到一个k,使前k个村庄建P - 1个邮局,最后几个村庄建一个邮局的方案满足题意。那么状态转移方程可以写为:

d p [ i ] [ j ] dp[i][j] dp[i][j]:前i个村庄建j个邮局的最小距离和

d i s [ i ] [ j ] dis[i][j] dis[i][j]:第i个村庄到第j个村庄之间建1个邮局的最小距离和
d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ k ] [ j − 1 ] + d i s [ k + 1 ] [ j ] ) dp[i][j] = min(dp[i][j],dp[k][j - 1] + dis[k + 1][j]) dp[i][j]=mindp[i][j]dp[k][j1]+dis[k+1][j]

那么还剩下一个难点,也就是 d i s [ i ] [ j ] dis[i][j] dis[i][j]的计算,如何来求前i个村庄建j个邮局的最小距离和。计算 d i s [ i ] [ j ] dis[i][j] dis[i][j]时, d i s [ i ] [ j − 1 ] dis[i][j - 1] dis[i][j1]已经计算出来,而且可以推导出无论j - 1为奇数还是偶数, d i s [ i ] [ j ] dis[i][j] dis[i][j]均可以写成 d i s [ i ] [ j − 1 ] dis[i][j - 1] dis[i][j1] + j距离i、j中点村庄的距离。验证一下:

假设我们输入的位置数组是 a [ ] a[] a[] a [ i ] a[i] a[i]即第i个村庄的位置。

如果有 n 1 , n 2 , n 3 , n 4 , n 5 n1,n2,n3,n4,n5 n1,n2,n3,n4,n5四个点,只能建一个邮局,那么应该如何计算?上面已经说了建在中点,也就是 n 3 n3 n3处,此时应该为(回忆一下上面的规律)

d i s [ 1 ] [ 5 ] = a [ 5 ] + a [ 4 ] − a [ 2 ] − a [ 1 ] dis[1][5]=a[5]+a[4]-a[2]-a[1] dis[1][5]=a[5]+a[4]a[2]a[1]

对比一下 d i s [ 1 ] [ 4 ] = a [ 4 ] + a [ 3 ] − a [ 2 ] − a [ 1 ] dis[1][4]=a[4]+a[3]-a[2]-a[1] dis[1][4]=a[4]+a[3]a[2]a[1],多了一个 a [ 5 ] − a [ 3 ] a[5]-a[3] a[5]a[3]的项,也就是当前点5和[1,5]中间点的距离。总结一下:

d i s [ i ] [ j ] = d i s [ i ] [ j − 1 ] + a [ i ] − a [ ( i + j ) / 2 ] dis[i][j]=dis[i][j-1]+a[i]-a[(i+j)/2] dis[i][j]=dis[i][j1]+a[i]a[(i+j)/2]

不妨再加一个点 n 6 n6 n6 d i s [ 1 ] [ 6 ] = a [ 6 ] + a [ 5 ] + a [ 4 ] − a [ 3 ] − a [ 2 ] − a [ 1 ] dis[1][6]=a[6]+a[5]+a[4]-a[3]-a[2]-a[1] dis[1][6]=a[6]+a[5]+a[4]a[3]a[2]a[1]完全成立。到这里问题基本上就结束了。。。预处理 d i s dis dis,然后对 d p [ i ] [ j ] = m i n ( d p [ i ] [ j ] , d p [ k ] [ j − 1 ] + d i s [ k + 1 ] [ j ] ) dp[i][j] = min(dp[i][j],dp[k][j - 1] + dis[k + 1][j]) dp[i][j]=mindp[i][j]dp[k][j1]+dis[k+1][j]遍历所有可能存在的k进行DP即可。

#include<iostream>
#include<cstdio>
#include<string>
#include<string.h>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;

#define MAX 305
#define ll long long
#define inf 10000005

int v[MAX], dp[MAX][35], V, P, dis[MAX][MAX];

int main() {
	scanf("%d%d", &V, &P);
	for (int i = 1; i <= V; i++)scanf("%d", &v[i]);
	for (int i = 0; i <= V; i++)for (int j = 0; j <= P; j++)dp[i][j] = inf;

	for (int i = 1; i <= V; i++) {
		for (int j = i + 1; j <= V; j++) {
			dis[i][j] = dis[i][j - 1] + v[j] - v[(i + j) >> 1];
		}
	}

	for (int i = 1; i <= V; i++)dp[i][1] = dis[1][i];
	for (int i = 1; i <= V; i++) {//前i个村庄
		for (int j = 2; j <= P; j++) {//前i个村庄建立j个邮局
			for (int k = j - 1; k < i; k++)//前k(1+j<k<i)个村庄建立j-1个邮局
				dp[i][j] = min(dp[i][j], dp[k][j - 1] + dis[k + 1][i]);
		}
	}

	printf("%d\n", dp[V][P]);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值