好的,我又来了,站内全文
有N个村庄排列在一条直线上
需要在部分村庄建立不超过K个基站
每个村庄有建站成本(Ci)和覆盖范围(Si)
未被覆盖的村庄需要支付补偿费(Wi)
目标:选择基站位置,使总费用最小(建站费+补偿费)
覆盖范围:在村庄i建站,能覆盖距离在[Di−Si,Di+Si]范围内的村庄
未覆盖补偿:如果一个村庄不在任何基站的覆盖范围内, 就要支付Wi
距离特性:D2,D3,...Dn是递增的(村庄按顺序排列)
A[问题分析] --> B[动态规划]
B --> C[状态定义]
B --> D[状态转移]
D --> E[优化方案]
定义dp[i][k]:表示在前i个村庄中建立k个基站,且最后一个基站在位置i时的最小总费用
dp[i][k] = min{ dp[j][k-1] + cost(j+1, i-1) } + C[i]
(其中j < i)
C[i]:在i处建站的费用
cost(j+1, i-1):j和i之间的村庄未被覆盖的补偿费
A[朴素DP] -->|O(n²)| B[超时]
A -->|优化| C[线段树]
C --> D[区间查询]
C --> E[区间更新]
D --> F[O(log n)查询]
E --> G[O(log n)更新]
添加虚拟村庄(简化边界处理)
n++;
D[n] = INF; // 很大的数
C[n] = 0; S[n] = 0; W[n] = 0;
计算覆盖范围(使用二分查找)
for (每个村庄i) {
左边界L[i] = 第一个位置≥(D[i]-S[i])的村庄
右边界R[i] = 最后一个位置≤(D[i]+S[i])的村庄
}
村庄分组(按右边界分组)
vector<int> groupByRight[MAXN];
for (每个村庄i) {
groupByRight[R[i]].push_back(i);
}
// 初始化DP数组
dp[0] = 0; // 没有村庄时费用为0
for (i=1 to n) dp[i] = INF; // 初始化为无穷大
// 枚举基站数量k
for (k = 1; k <= K+1; k++) {
// 初始化线段树
SegmentTree seg;
seg.build(dp);
// 处理每个村庄
for (i = 1; i <= n; i++) {
// 处理右边界为i-1的村庄
for (每个在groupByRight[i-1]中的村庄v) {
seg.update(0, L[v]-1, W[v]); // 更新补偿费
}
// 查询最小值并更新状态
min_val = seg.query(0, i-1);
if (min_val < INF) {
new_dp[i] = min_val + C[i];
}
}
}
作用:高效处理区间查询(最小值)和区间更新(增加补偿费)
优势:将时间复杂度从O(n2)降低到O(nlogn)
确保所有村庄都能被处理
解决边界条件问题
不影响最终结果(建站费用为0)
按右边界分组村庄
处理时只需关注当前右边界
减少不必要的计算
| 步骤 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 预处理 | O(nlogn) | O(n) |
| DP主循环 | O(K⋅nlogn) | O(n) |
| 线段树操作 | O(logn) | O(n) |
| 总计 | O(K⋅nlogn) | O(n) |
先掌握基础动态规划
学习线段树数据结构
理解二分查找的应用
练习简单版本的基站问题(小规模数据)
使用小规模数据测试(N=5,K=2)
打印中间结果(覆盖范围、分组情况)
验证线段树操作是否正确
检查边界条件处理
忽略村庄排序特性
未正确处理边界情况
线段树更新/查询范围错误
内存分配不足(N最大20000)