最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一个长度为 n 的数组 w,以及一个正整数 m
其中 wi 表示第 i 个 元素 的 价值
求一种选择元素的 方案:
- 使得选择的 相邻元素 之间相差 不超过 m−1 个 不选 的元素
- 选择的元素总贡献 最小
分析
求 一维数组选择方案,不妨用 动态规划 将问题拆分成一个个 子问题 来求解,最后 合并
状态表示-集合 fi:以 i 为 右端点 的 前缀区间,选择 第 i 个元素的 方案
状态表示-属性 fi:方案选的元素 总贡献最小 Min
状态计算 :
fi=min
有限范围 的 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
雀食是等价的
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 200010; int n, m; int w[N], dp[N], q[N]; int main() { cin >> n >> m; for (int i = 1; i <= n; i ++ ) cin >> w[i]; int hh = 0, tt = -1; q[ ++ tt ] = 0; for (int i = 1; i <= n + 1; i ++ ) { if (hh <= tt && q[hh] < i - m) hh ++; dp[i] = dp[q[hh]] + w[i]; while (hh <= tt && dp[q[tt]] >= dp[i]) tt --; q[ ++ tt ] = i; } cout << dp[n + 1] << endl; return 0; }
贴份我的代码
如果把弹出队头那一句放在最后,先进去再出来,然后判断条件改为>=m,就可以在for循环结束后保证维护的单调队列大小为m,就不用在最后继续弹出,直接输出就行。
是在铅笔代码基础上进行等价变换
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 ++ ) { f[i] = f[que[hh]] + w[i]; while (hh <= tt && f[que[tt]] >= f[i]) tt -- ; que[ ++ tt] = i; if (hh <= tt && i - que[hh] >= m) hh ++ ; } //if (n + 1 - m > que[hh]) hh ++ ; //滑动窗口往后滑动一位 printf("%d\n", f[que[hh]]); return 0;
}
感觉最后一步滑动窗口应该n + 1 - que[hh] > m 即当滑动窗口中的元素个数大于m就弹出元素qwq,但两者都可以过ovo
谢谢指出,已更正
大佬,请教一下这里为什么要tt=0? 按y总的方式一般不是hh=0,tt=-1吗?求解啊。
第一个元素直接初始化,
q[0] = 0
噢,原来这样,感谢大佬。
dp这章就靠大佬了
加油hh
感觉hh已经成为了ACWING的一大特色了
hh
hh