The Odyssey of DP —— NOI题集拾遗

NOI 162:Post Office

在一条直线坐标轴上有V个村庄,P个邮局,邮局建在村庄上,求一种建法,让V个村庄到最近邮局的距离之和最小。
输入:第一行包括两个整数, 1 <= V <= 300,1 <= P <= 30;第二行输入有序的村庄的位置a[i]
输出:输出最小的V个村庄到最近邮局的距离之和
输入
10 5
1 2 3 6 7 9 11 22 44 50
输出
8

首先需要考虑到,如果对$[0, \cdots , i]$的村庄只建一座邮局,那一定是把邮局建在中央的村庄[i/2]中使得到邮局的距离和最小。以下给出证明:
设对于$[0, \cdots , i]$的村庄,将邮局建在$[i/2]$中得到的距离和为S,下标为i的村庄的位置为a[i]。假设只建一座邮局时,邮局建筑的最优位置为$[j](j \ne i)$,则邮局建在[j]的距离和为$S+ abs(a[i/2] - a[j]) \times (i - i/2 - j)$,因为 $S+ abs(a[i/2] - a[j]) \times (i - i/2 - j) > S$,与 [j]为最优位置矛盾,所以 j = i / 2,即得证。
我们可以设置$dp[i][j] := [0, \cdots , i]$村庄中建j座村庄的距离和最小值。由之前证得的定理很容易得到状态转移方程:(min_distance(i,j)表示在$[i, \cdots, j]$建一座邮局的距离和最小值)

即对$0 \le m < i ,[0, \cdots, m]$的村庄建j-1个邮局,$[m+1, \cdots, i]$的村庄建一个邮局,遍历m,得到距离和最小值。代码如下,时间复杂度为$O(n^3)$:

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
#pragma once
#include<iostream>
#include<cstdio>
#include<math.h>
#include<algorithm>
using namespace std;
int p, v;
int villages[310];
int dp[310][31];// dp[i][j] 表示[0, ... , i]中j个post office的cost
const int INF = 1000000000;
int min_distance(int i, int j)
{
int mid = villages[(i + j) / 2];
int ans = 0;
for (int k = i; k <= j; k++)
{
ans += abs(villages[k] - mid);
}
return ans;
}
void NOI_2_6_162()
{
scanf("%d%d", &v, &p);
for (int i = 0; i < 310; i++)
{
for (int k = 0; k < 31; k++)
{
dp[i][k] = INF;
}
}
for (int i = 0; i < v; i++)
{
scanf("%d", &villages[i]);
}
for (int i = 0; i < v; i++)
{
dp[i][1] = min_distance(0, i);
}
for (int k = 2; k <= p; k++)
{
for (int i = 0; i < v; i++)
{
for (int m = 0; m < i; m++)
{
dp[i][k] = min(dp[i][k], dp[m][k - 1] + min_distance(m + 1, i));
}
}
}
printf("%d\n", dp[v-1][p]);
}

NOI 7627:鸡蛋的硬度

有n层楼,m个鸡蛋,如果鸡蛋从第a层摔下来没摔破,但是从a+1层摔下来时摔破了,那么就说这个鸡蛋的硬度是a。这些鸡蛋硬度相同,在求鸡蛋的硬度下问使用最优策略在最坏情况下所需要的扔鸡蛋次数。
输入
100 1
100 2
输出
100
14