先预处理一段村庄只建一个邮局的代价,再做邮局数量分层 DP,并用决策单调性做分治优化。
OJ: luogu
题目 ID: P4767
难度:提高+/省选-
标签:动态规划决策单调性分治优化区间
日期: 2026-06-21 12:31
题意
一条数轴上有 V 个村庄,位置已知,要在其中一些村庄上建 P 个邮局。
每个村庄会去离自己最近的邮局,要求所有村庄到最近邮局的距离和最小。
思路
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int v, p;
int x[35];
int cost[35][35];
int dp[35][35];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> v >> p;
for (int i = 1; i <= v; i++) {
cin >> x[i];
}
sort(x + 1, x + v + 1);
for (int l = 1; l <= v; l++) {
for (int r = l; r <= v; r++) {
int mid = (l + r) >> 1;
for (int i = l; i <= r; i++) {
cost[l][r] += abs(x[i] - x[mid]);
}
}
}
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
for (int k = 1; k <= p; k++) {
for (int i = 1; i <= v; i++) {
for (int j = 0; j < i; j++) {
dp[k][i] = min(dp[k][i], dp[k - 1][j] + cost[j + 1][i]);
}
}
}
cout << dp[p][v] << '\n';
return 0;
}先把村庄位置排序。
如果一段连续村庄 [l, r] 只建一个邮局,最优位置一定在中位数村庄。
所以可以先预处理:
cost[l][r]:负责区间[l, r]的所有村庄时,只建一个邮局的最小总代价
接下来做 DP。
设:
dp[k][i]:前i个村庄建k个邮局时的最小总代价
最后一个邮局负责区间 [j+1, i],则有转移:
dp[k][i] = min(dp[k-1][j] + cost[j+1][i])
朴素枚举 j 的复杂度是
这题满足决策单调性,可以用分治优化。
也就是说:
dp[k][i]的最优决策点- 不会在
i变大时向左跳
于是每一层 k 的 DP,都可以用一次分治递归在
所以整体就能通过 V <= 3000 的数据。
DP 转移方程
核心状态:
dp[k][i] 为前 i 村建 k 个邮局的最小代价
核心转移:
dp[k][i]=min_j dp[k-1][j]+cost[j+1][i]
答案收束:
dp[P][V]
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 3005;
const int MAXP = 305;
const int INF = 0x3f3f3f3f;
int v, p;
int x[MAXN];
int cost[MAXN][MAXN];
int dp[MAXP][MAXN];
// 分治优化:
// 计算 dp[layer][l..r],最优决策点在 [opt_l, opt_r] 中。
void solve_layer(int layer, int l, int r, int opt_l, int opt_r) {
if (l > r) {
return;
}
int mid = (l + r) >> 1;
int best_k = -1;
dp[layer][mid] = INF;
int right = min(mid - 1, opt_r);
for (int k = opt_l; k <= right; k++) {
int val = dp[layer - 1][k] + cost[k + 1][mid];
if (val < dp[layer][mid]) {
dp[layer][mid] = val;
best_k = k;
}
}
solve_layer(layer, l, mid - 1, opt_l, best_k);
solve_layer(layer, mid + 1, r, best_k, opt_r);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> v >> p;
for (int i = 1; i <= v; i++) {
cin >> x[i];
}
sort(x + 1, x + v + 1);
// 预处理一段区间只建一个邮局时的最小总代价。
// 最优邮局位置是中位数点。
for (int l = v; l >= 1; l--) {
for (int r = l; r <= v; r++) {
int mid = (l + r) >> 1;
cost[l][r] = cost[l][r - 1] + x[r] - x[mid];
}
}
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0;
for (int i = 1; i <= v; i++) {
dp[1][i] = cost[1][i];
}
for (int layer = 2; layer <= p; layer++) {
solve_layer(layer, layer, v, layer - 1, v - 1);
}
cout << dp[p][v] << '\n';
return 0;
}复杂度
预处理 cost 是
DP 分治优化总复杂度大致是
空间复杂度是
总结
这题是经典“邮局问题”。
核心分两步:
- 先把“一段村庄只建一个邮局”的代价预处理出来
- 再把多邮局问题写成分层 DP,并利用决策单调性优化
理解这题之后,很多“四边形不等式 / 决策单调性”的 DP 都会更容易上手。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
