poj-1160-Post-Office-经典DP

缘起

日常浪费生命~ poj 1160 Post Office

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
一条高速公路,有N个村庄,每个村庄均有一个唯一的坐标,选择P个村庄建邮局,问怎么选择,
才能使每个村庄到其最近邮局的距离和最小?最后打印这个最小值。

【输入】
第一行是N和P,1 <= N <= 300, 1 <= P <= 30,第二行是N个单调递增的整数X,1 <= X <= 10000.
表示村庄的坐标

【输出】
最小距离之和

【样例输入】
10 5
1 2 3 6 7 9 11 22 44 50

【样例输出】
9

【限制】
1s

经典DP了. 令

1
2
3
4
5
6
7
8
9
dp[i][j]表示前i个村庄建立j个邮局的答案.则
dp[i][j]=min{dp[k][j-1]+w[k+1][i],j-1<=k<i}
为什么这个转移方程是正确的呢? 因为根据初中数学知识, n个数轴上的点, 要选择一个点使得这n个点到这个点的
距离之和最小. 则选择的点就是这n个点中间的点(n为偶数的话, 随便中间2个点的哪个点都行).
我们其实可以换个角度看本题——即最后你选择的那个邮局(即X最大的那个邮局)它的影响范围是从第k+1个邮局
到第i个邮局,也就是[k+1,...,i] 就是要到最后那个邮局投递的. 而前[1,..,k]这k个村庄建立j-1个邮局
最短距离之和是 dp[k][j-1], 所以上述DP转移方程是正确的.
dp[1][1]=w[1][1]=0, dp[2][1] = w[1][2],..., dp[n][1]=w[1][n]
最后的答案就是dp[n][p]

知道了转移方程, 代码就巨好写无比了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//#include "stdafx.h"
#include <stdio.h>
#include <algorithm>
using namespace std;
//#define LOCAL
#define ABS(x) (((x)>0)?(x):(-(x)))

int n,p, w[305][305], dp[305][35], x[305], inf = ~0u>>1;

int getw(int i, int j)
{
int ans = 0;
int m = (i+j)>>1;
for (int k = i; k<=j; k++)
{
ans+= ABS(x[k]-x[m]);
}
return ans;
}

int main()
{
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
// freopen("d:\\my.out", "w", stdout);
#endif
scanf("%d%d", &n, &p);
for (int i = 1; i<=n; i++)
{
scanf("%d", x+i);
}
for (int i = 1; i<n; i++)
{
for (int j = i+1; j<=n; j++)
{
w[i][j] = getw(i,j);
}
} // 初始化w
for (int i =1; i<=n; i++)
{
dp[i][1] = w[1][i];
} // dp初始化,即考察建立一个邮局的情况
for (int j = 2; j<=p; j++) // 建立一个邮局的情况已经全部考察完毕, 现在考察建立2个邮局
{
for (int i = j; i<=n; i++)
{
dp[i][j] = inf;
for (int k = j-1; k<i; k++)
{
dp[i][j] = min(dp[i][j], dp[k][j-1]+w[k+1][i]);
}
}
} // 进行DP
printf("%d\n", dp[n][p]);
return 0;
}

ac情况

Status Accepted
Time 32ms
Memory 728kB
Length 944
Lang G++
Submitted 2019-10-04 17:48:37
Shared
RemoteRunId 20925759