题目大意
题目链接
给出一条直线上的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=n2−n1+n3−n1+n4−n1=(n2+n3+n4)−3∗n1
- 考虑建在边缘节点 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=n4−n1+n4−n2+n4−n3=3∗n4−(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 n2−n1+n3−n2+n4−n2=n3+n4−n1−n2<n3+n4−2∗a1<n2+n3+n4−3∗n1<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=n3−n1+n3−n2+n4−n3=n3+n4−n1−n2<2∗n4−n1−n2<3∗n4−n1−n2−n3<d2
显然在任何一个中间点建立邮局效果都一样,都小于在端点处建立邮局。还注意这个形式哈,如果是5个节点,那么最小距离是这样的: n 5 + n 4 − n 2 − n 1 n5+n4-n2-n1 n5+n4−n2−n1,有没有感受到什么,从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+nk−1+...+n(k+1)/2+1−n(k+1)/2−1−...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+nk−1+...+nk/2+1−nk/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]=min(dp[i][j],dp[k][j−1]+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][j−1]已经计算出来,而且可以推导出无论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][j−1] + 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][j−1]+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]=min(dp[i][j],dp[k][j−1]+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]);
}