最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一个长度为 $n$ 的数组 $w$,以及一个正整数 $m$
其中 $w_i$ 表示第 $i$ 个 元素 的 价值
求一种选择元素的 方案:
- 使得选择的 相邻元素 之间相差 不超过 $m-1$ 个 不选 的元素
- 选择的元素总贡献 最小
分析
求 一维数组选择方案,不妨用 动态规划 将问题拆分成一个个 子问题 来求解,最后 合并
状态表示-集合 $f_i$:以 $i$ 为 右端点 的 前缀区间,选择 第 $i$ 个元素的 方案
状态表示-属性 $f_i$:方案选的元素 总贡献最小 $Min$
状态计算 :
$$ \begin{aligned} &f_i = \min\{{ f_j + w_i }\} & (0 \le i - j - 1 \le m-1) \\\\ ~ \\\\ \Rightarrow \quad &f_i = w_i + \min\{{f_j}\} & (1 \le i - j \le m) \end{aligned} $$
有限范围 的 $j$ 和分离开常量后的 最小值函数,直接套 单调队列模板 即可
然后是关于如何 统计答案
y 总演示的是在最后,暴力枚举 最终状态 f[i]
,其中 $n - m + 1 \le i \le n$ (y总常数有点大)
这里可以巧妙利用我们的 滑动窗口
首先我们知道,单调队列 维护的是 $i - m \le j \le i - 1$ 的区间的 $f_j$ 状态最小值
循环迭代完第 $n$ 轮后,$j$ 的范围为 $n - m \le j \le n$
$f[n]$ 进入滑动窗口,但是我们的代码中,判断滑动窗口的大小要在 $n+1$ 轮里进行
所以结束的时候,把滑动窗口往后多划一位,把 $j$ 的范围定在 $n - m + 1 \le j \le n$
则 单调队列 的 队头元素,就是该范围内的 值最小的最终状态,输出 队头元素 即可
Code
时间复杂度: $O(n)$
初始状态: $f_0$
目标状态: $f_j \quad (n - m + 1 \le j \le n)$
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
int n, m;
int w[N], f[N];
int que[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
int hh = 0, tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (hh <= tt && i - que[hh] > m) hh ++ ;
f[i] = f[que[hh]] + w[i];
while (hh <= tt && f[que[tt]] >= f[i]) tt -- ;
que[ ++ tt] = i;
}
if (n + 1 - m > que[hh]) hh ++ ; //滑动窗口往后滑动一位
printf("%d\n", f[que[hh]]);
return 0;
}
把修剪草坪 的m-1,然后输出s[n] - f[n]就能过
+1
雀食是等价的
贴份我的代码
如果把弹出队头那一句放在最后,先进去再出来,然后判断条件改为>=m,就可以在for循环结束后保证维护的单调队列大小为m,就不用在最后继续弹出,直接输出就行。
是在铅笔代码基础上进行等价变换
int main()
{
scanf(“%d%d”, &n, &m);
for (int i = 1; i <= n; i ++ ) scanf(“%d”, &w[i]);
}
感觉最后一步滑动窗口应该n + 1 - que[hh] > m 即当滑动窗口中的元素个数大于m就弹出元素qwq,但两者都可以过ovo
谢谢指出,已更正
大佬,请教一下这里为什么要tt=0? 按y总的方式一般不是hh=0,tt=-1吗?求解啊。
第一个元素直接初始化,
q[0] = 0
噢,原来这样,感谢大佬。
dp这章就靠大佬了
加油hh
感觉hh已经成为了ACWING的一大特色了
hh
hh