AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 1089. 烽火传递【单调队列优化DP + 目标状态小优化】    原题链接    中等

作者: 作者的头像   一只野生彩色铅笔 ,  2021-09-21 16:02:00 ,  所有人可见 ,  阅读 3853


49


4

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

给定一个长度为 $n$ 的数组 $w$,以及一个正整数 $m$

其中 $w_i$ 表示第 $i$ 个 元素 的 价值

求一种选择元素的 方案:

  1. 使得选择的 相邻元素 之间相差 不超过 $m-1$ 个 不选 的元素
  2. 选择的元素总贡献 最小

分析

求 一维数组选择方案,不妨用 动态规划 将问题拆分成一个个 子问题 来求解,最后 合并

状态表示-集合 $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;
}

17 评论


用户头像
Hwretcheds   2022-07-24 09:36      4    踩      回复

把修剪草坪 的m-1,然后输出s[n] - f[n]就能过

用户头像
Fzldq   2022-10-26 12:58         踩      回复

+1

用户头像
椎名まひる   2023-10-24 16:18      1    踩      回复

雀食是等价的


用户头像
Leisure_fan   2024-03-26 01:35      2    踩      回复
#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;
}
用户头像
Leisure_fan   2024-03-26 01:36      1    踩      回复

贴份我的代码


用户头像
zzuzhou   2024-02-03 17:27         踩      回复

如果把弹出队头那一句放在最后,先进去再出来,然后判断条件改为>=m,就可以在for循环结束后保证维护的单调队列大小为m,就不用在最后继续弹出,直接输出就行。

用户头像
zzuzhou   2024-02-03 17:29         踩      回复

是在铅笔代码基础上进行等价变换
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;

}


用户头像
我要AK   2022-01-04 17:26         踩      回复

感觉最后一步滑动窗口应该n + 1 - que[hh] > m 即当滑动窗口中的元素个数大于m就弹出元素qwq,但两者都可以过ovo

用户头像
一只野生彩色铅笔   2022-01-04 18:02      1    踩      回复

谢谢指出,已更正


用户头像
ACer.   2021-12-30 11:08         踩      回复

大佬,请教一下这里为什么要tt=0? 按y总的方式一般不是hh=0,tt=-1吗?求解啊。

用户头像
一只野生彩色铅笔   2021-12-30 11:29      1    踩      回复

第一个元素直接初始化,q[0] = 0

用户头像
ACer.   2022-05-05 15:20    回复了 一只野生彩色铅笔 的评论         踩      回复

噢,原来这样,感谢大佬。


用户头像
tryflow   2021-09-21 17:38         踩      回复

dp这章就靠大佬了

用户头像
一只野生彩色铅笔   2021-09-21 23:08         踩      回复

加油hh

用户头像
蒟蒻999   2023-06-25 15:21    回复了 一只野生彩色铅笔 的评论         踩      回复

感觉hh已经成为了ACWING的一大特色了

用户头像
jkjk666   2024-07-14 15:29    回复了 蒟蒻999 的评论      1    踩      回复

hh

用户头像
神崎日照   2024-10-12 16:14    回复了 蒟蒻999 的评论         踩      回复

hh


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息