dp 单调队列 线性dp 烽火传递 - LibreOJ 10180 - Virtual Judge (csgrandeur.cn) 烽火台是重要的军事防御设施,一般建在交通要道或险要处。一旦有军情发生,则白天用浓烟,晩上有火光传递军情。 在某两个城市之间有 座烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确传递,在连续 个烽火 台中至少要有一个发出信号。现在输入 和每个烽火台的代价,请计算总共最少的代价在两城市之间来准确传递情报。

输入格式

第一行是 ,表示 个烽火台和连续烽火台数 ; 第二行 个整数表示每个烽火台的代价

输出格式

输出仅一个整数,表示最小代价。

样例

5 3
1 2 5 6 2
4

对于全部数据,

思路

一维线性问题通常都可以进行子问题划分, 这里则用类似于最长上升子序列问题LST的状态划分方式。

状态表示: f[i] 1-i中选, 结尾为i且选中的最小价值 状态计算: f[i] = min(f[j] + w[i]) j in [i - m,i-1] 对于所在的区间是合法的, 能转移到i且保证合法的区间是 之前的长度为区间内选一个, 不然会导致i所在区间和前一个区间中间有遗漏

显然这里我们没必要真的用双重循环, 因为选中的是最小值, 且是在i之前m长度中的任意一个, 即区间最值问题。 使用单调队列优化。

\begin{eqnarray} f_{i} & = & \min \left\{f_{j}+w_{i}\right\} & (0 \leq i-j-1 \leq m-1) \\ \Rightarrow & f_{i} & = w_{i}+\min \left\{f_{j}\right\} &(1 \leq i-j \leq m) \end{eqnarray}

结果状态为 也可以利用单调队列的特性, 最后是枚举到 [n - m, n - 1]这个区间, 若q[hh] < n + 1 - m , 则hh++, 那么 f[q[hh]] 就是答案。

这里用, 是因为需要计算, 把先入队, 避免出现的起始问题

队头边界条件为 是因为区间总长度带上, 而 所计算的区间长度是实际长度减一, 故条件为 , 化简一下就是
总结一下
在i之前长度为的区间是 , 包含长度为的区间是
在i之前长度为的区间是 , 包含长度为的区间是
若题目是小于的区间, 则替换为即可。

代码

#include <iostream>
using namespace std;
const int N = 2e5 + 10;
int a[N];
int q[N], hh, tt;
int f[N];
int n, m;
 
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
 
    hh = 0, tt = 0;
    for (int i = 1; i <= n; i++)
    {
        if (q[hh] < i - m)
            hh++;
        f[i] = f[q[hh]] + a[i];
        while (hh <= tt && f[q[tt]] >= f[i])
            tt--;
        q[++tt] = i;
    }
 
    if (n - m + 1 > q[hh])
        hh++;
    cout << f[q[hh]] << endl;
    return 0;
}