以i
结尾的f[i]
,滑动窗口的区间是[i - m,i - 1]
,单调队列维护的是该区间的最小值,由于滑动窗口不包含i
,因此f[i]
需要在while
上方进行更新
一、简单DP
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 200010;
int f[N];
int w[N], n, m;
int main() {
/**
普通DP大法
状态表示:
①集合:f[i]表示前i个灯塔满足条件时,并且i点亮。
②属性:最小代价
状态计算:f[i]=min(f[j]+w[i]) (i−m≤j≤i−1)
答案:(n−m+1)~n必须有灯塔亮,所以枚举一下求出最小值即可
*/
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i];
//初始化
memset(f, 0x3f, sizeof f);//预求最小,先设最大
f[0] = 0;//边界点
for (int i = 1; i <= n; i++)//遍历每一个烽火台
for (int j = max(0, i - m); j <= i - 1; j++)//向前查看它之前的m个
f[i] = min(f[i], f[j] + w[i]);
int res = INF;
//A城市0号,B城市:n+1号
//极限思考法的话,可以设m=2,通过画图,就可以推出就是n+1-m为可能的位置
//这是一个常用的套路:从最终可能合理的位置找一遍!
//注意这个起点,表示如果最后一个位置是n+1-m的话,也是合理解,可以覆盖掉第n个
for (int i = n + 1 - m; i <= n; i++) res = min(res, f[i]);
cout << res << endl;
return 0;
}
二、滑动窗口优化的DP
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
const int INF = 0x3f3f3f3f;
int f[N];
int w[N], n, m;
int q[N];
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i];
int hh = 0, tt = 0;
//假设城市A算作一个编号为0且代价为0的灯塔
for (int i = 1; i <= n; i++) { //遍历每一个烽火台
if (tt >= hh && i - q[hh] > m) hh++;
//为什么要先更新?
f[i] = f[q[hh]] + w[i];
//踢出
while (tt >= hh && f[q[tt]] >= f[i])tt--;
q[++tt] = i;
}
//同样城市也算一个代价为0的灯塔,城市前m的灯塔至少有个点亮
int res = INF;
for (int i = n + 1 - m; i <= n; i++)
res = min(res, f[i]);
cout << res << endl;
return 0;
}