1 条题解
-
0
1. 题意理解
有 个哨站,第 个哨站的频段是 。
每个哨站 可以:
- 直接连到控制中心,代价 。
- 连到前面的某个哨站 (),代价 。
限制:每个哨站只能被至多一个后面的哨站连接(即每个哨站作为父节点最多一个子节点)。
要求总代价最小。
2. 问题结构分析
这其实是一个树形结构:控制中心是根节点(节点 ),哨站 是子节点。
每个哨站要么是根的直接孩子(代价 ),要么是某个前面哨站的孩子(代价 )。由于“每个哨站只能被后面的至多一个哨站连接”,意味着如果我们把哨站按 排列,连接关系形成若干条链(每个链的尾部是某个哨站,它后面没有连它)。
3. 动态规划思路
设 表示考虑前 个哨站,且第 个哨站是它所在链的最后一个节点(即没有被后面的哨站连接)时,前 个哨站的最小总代价。
那么转移时,我们考虑第 个哨站所在的链:
- 可能第 个哨站直接连控制中心:代价 。
- 可能第 个哨站连到前面的某个哨站 (),那么 到 之间的所有哨站()必须自己作为链尾(即它们各自要么连控制中心,要么连更前面的哨站,但不会连到 到 之间的哨站,因为 是链尾)。
所以我们可以枚举链的起点 (),表示从 到 形成一条链,链头 连控制中心(代价 ),链中 连 , 连 ,…, 连 ,这条链的总代价为:
前 个哨站的最小代价是 ( 是链尾)。
特殊情况: 时,(没有哨站,代价 0)。
4. 状态转移方程
因此:
$$dp[i] = \min_{1 \le k \le i} \left\{ dp[k-1] + W + \sum_{t=k}^{i-1} |a_{t+1} - a_t| \right\} $$初始:。
答案:。
5. 复杂度优化
直接计算是 ( 从 到 , 从 到 ,求和又是 ),对于 可能太慢。
我们可以预处理差分绝对值的前缀和来优化:
设
那么
$$\sum_{t=k}^{i-1} |a_{t+1} - a_t| = pref[i-1] - pref[k-1] $$于是转移变成:
$$dp[i] = \min_{1 \le k \le i} \left\{ dp[k-1] + W + pref[i-1] - pref[k-1] \right\} $$即:
$$dp[i] = pref[i-1] + W + \min_{1 \le k \le i} \left\{ dp[k-1] - pref[k-1] \right\} $$我们可以在遍历 时维护 ,这样转移就是 。
总复杂度 。
6. 正确性验证
样例 1
计算 :
(下标从 1 开始)
,
:
,
:
,
:
,
:
,
:
,
输出 ,符合样例。
7. 代码实现
#include <iostream> #include <vector> #include <cmath> #include <algorithm> using namespace std; int main() { int n; long long W; cin >> n >> W; vector<long long> a(n+1); for (int i = 1; i <= n; i++) { cin >> a[i]; } vector<long long> diff(n+1, 0); vector<long long> pref(n+1, 0); for (int i = 1; i <= n-1; i++) { diff[i] = abs(a[i+1] - a[i]); pref[i] = pref[i-1] + diff[i]; } vector<long long> dp(n+1, 0); long long min_val = 0; // dp[0] - pref[0] = 0 - 0 = 0 for (int i = 1; i <= n; i++) { dp[i] = pref[i-1] + W + min_val; min_val = min(min_val, dp[i] - pref[i]); } cout << dp[n] << endl; return 0; }
8. 总结
这道题的关键在于:
- 将问题转化为链式连接,定义 为前 个哨站且第 个是链尾的最小代价。
- 通过枚举链的起点,得到转移方程。
- 利用前缀和优化,将复杂度从 降到 。
最终算法非常高效,可以处理 的规模,而题目只到 ,绰绰有余。
- 1
信息
- ID
- 4937
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者