1 条题解
-
0
思路与算法
把已经发信号的烽火台作为选中的位置。任意连续m个中至少选一个,等价于首段、相邻选中点之间、尾段的未选数量都小于m。
在位置0放一个代价为0的虚拟起点。定义dp[i]为最后一个选中位置是i时的最小费用,则dp[i]=a[i]+min(dp[j]),其中max(0,i-m)≤j≤i-1。单调队列维护这段窗口的最小dp:删去过期下标,用队首转移,再淘汰队尾不优于新点的候选并加入i。
在n+1放代价为0的虚拟终点。它的合法前驱范围为max(0,n+1-m)..n,答案是此范围的最小dp。代码不另存终点,只在末尾更新队首有效范围后输出。按照题面给出的独立n、m范围,m>n时没有完整的m长窗口,可以不选任何台,答案0。
正确性说明
若有一个m长窗口完全未选,它位于首段、尾段或两次选择之间,就会出现至少m个连续未选位置。反之,任何至少m个连续未选位置都包含一个这样的窗口。因此约束等价于含虚拟端点的相邻选择距离不超过m。
固定最后选中i,前一次选择j必须在上述范围内;加上i的费用之后,前面使用该j的最优方案即可得到最优dp[i]。转移枚举了所有合法前驱,故状态正确。
若旧候选j更早且费用不小于一个更新候选,新候选的未来费用不更差且过期更晚,旧候选可安全删除;队首因此始终给出窗口最小值。终点转移保证尾段也符合要求,得到全局最小费用。
复杂度
dp与队列数组初始化O(n)。每个位置入队一次、至多出队一次,全部转移和终点检查合计O(n)时间,空间O(n)。
易错点
过期条件为j<i-m,等于i-m的候选仍合法。只计算到dp[n]会错误强制最后一台发信号,必须对合法尾段取最小值。m=1时所有台都要选;m=n时至少选一台;m>n时虚拟起点可以直接到终点,代价为0。初始dp[0]=0,其余状态依次计算,不使用未计算的值。
参考代码(C++11)
#include <bits/stdc++.h> using namespace std; using ll = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; vector<ll> dp(n + 1); vector<int> q(n + 1); int head = 0, tail = 1; q[0] = 0; for (int i = 1; i <= n; ++i) { ll cost; cin >> cost; while (head < tail && q[head] < i - m) ++head; dp[i] = cost + dp[q[head]]; while (head < tail && dp[q[tail - 1]] >= dp[i]) --tail; q[tail++] = i; } while (head < tail && q[head] < n + 1 - m) ++head; cout << dp[q[head]] << '\n'; }
- 1