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

AcWing 1088. 旅行问题【单调队列优化DP】    原题链接    中等

作者: 作者的头像   一只野生彩色铅笔 ,  2021-09-21 14:06:06 ,  所有人可见 ,  阅读 3233


62


7

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

题目描述

给定一个 环,环 上有 $n$ 个节点,编号从 $1 \sim n$,以及一辆小车车

每一个 节点 $i$ 有一个 权值 $p_i$ 表示当车 到达该点 时,可以 获得 的 油量

还有一个 权值 $d_i$ 表示当车从 节点 $i$ 到 节点 $i+1$ 所需要 消耗 的 油量

现有一辆车想从环上 任意点 出发,顺时针 或 逆时针 绕环一圈走回起点

行驶的过程中,油量不能为 负数,初始油量 为 起点 处所能获得的 油量

判断能否完成 环圈行驶

分析

看到 环形问题,就会想到在先前 环形区间DP 中提到的一个解决方法:拉环成链

复制 一份数组接在 原数组 后面,这样原问题就 等价于 在 新数组 中 存在 长度为 $n + 1$ 的 合法区间

然后考虑区间何时 合法 的问题,题中提及:

  1. 会先在一个点 获得油量
  2. 然后 消耗油量 到达下一个点
  3. 消耗后的油量 不能小于 0

这极像一个我们熟悉的 括号序列模型 — 任意 前缀区间 必然满足 左括号数$\ge$右括号数

故想到用 前缀和思想 来分析:该 区间 如果 合法,则必然 任意前缀 获得油量 $\ge$ 消耗油量

这里我们要 明确 每个数组下标的实际含义,才能调对本题的 边界,不然会 WA 疯

先讨论 顺时针的走法

  1. $d[i]$:点 $i \rightarrow$ 点 $i+1$ 消耗 的 油量
  2. $p[i]$:到点 $i$ 能 获得 的 油量

相对应的 前缀和 含义:

  1. $sd[i]~(d[1 \cdots i])$:从 $1$ 出发到 $i+1$ 所 消耗 的 总油量
  2. $sp[i]~(p[1 \cdots i])$:从 $1$ 出发到 $i$ 所 获得 的 总油量

显然,我们要到 $i+1$ 点,需要满足 此前的剩余油量 + 该点获得的油量 $\ge$ 到达下点消耗的油量

因此,我们直接 比较 相同下标下的前缀和大小 即可

$i$ 到 $i+n$ 的耗油:$d[i] + d[i + 1] + \cdots + d[i + n - 1]$
$i$ 到 $i+n$ 的加油:$p[i] + p[i + 1] + \cdots + p[i + n - 1]$

闫氏DP分析法

状态表示-集合 $f_i$:以 $i$ 为 起点 和 终点,顺时针 走一圈的 方案

状态表示-属性 $f_i$:方案 是否可行 $true$

状态计算 :

$$ f_i = \bigg[\min\Big\{(sp_j - sp_{i-1}) - (sd_j - sd_{i-1})\Big\} \ge 0 \bigg] \quad (i \le j \le i+n-1) $$

和往常一样,提出 常量 :$f_i = \bigg[sd_{i - 1} - sp_{i-1} + \min\Big\{sp_j - sd_j\Big\} \ge 0 \bigg] \quad (i \le j \le i+n-1)$

这样就变成一个 滑动窗口求最小值 的问题了,直接套 单调队列 即可

再推导一下滑动窗口的范围:$1 \le i + n - j \le n$

故 滑动窗口 满足的 最大大小 为 $n$,且 先算后加

再讨论 逆时针的走法

镜像分析 一下就好了,不过下标有所变化

大家可以先 平移下标,继续 套上述公式;也可以 再重新推导一遍,强化一下自己对公式推导的能力

我这里就 重新推导 一遍了

$i + n$ 到 $i$ 的耗油:$d[i] + d[i + 1] + \cdots + d[i + n - 1]$
$i + n$ 到 $i$ 的加油:$p[i + 1] + p[i + 2] + \cdots + p[i + n]$

闫氏DP分析法

状态表示-集合 $f_i$:以 $i$ 为 起点 和 终点,逆时针 走一圈的 方案

状态表示-属性 $f_i$:方案 是否可行 $true$

状态计算 :

$$ f_i = \bigg[\min\Big\{(sp_{i+n} - sp_{j-1}) - (sd_{i+n-1} - sd_{j-2})\Big\} \ge 0 \bigg] \quad (i + 1 \le j \le i+n) \\\\ f_i = \bigg[sp_{i+n} - sd_{i+n-1} + \min\Big\{-sp_{j-1} + sd_{j-2}\Big\} \ge 0 \bigg] \quad (1 \le j - i \le n) $$

Code

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 1e6 + 10, M = N << 1;

int n;
int p[M], d[M], que[M];
LL sp[M], sd[M];
bool f[N];

LL g(int j)
{
    return sp[j] - sd[j];
}
LL h(int j)
{
    return sd[j - 2] - sp[j - 1];
}
LL get_val1(int i, int j)
{
    return sd[i - 1] - sp[i - 1] + g(j);
}
LL get_val2(int i, int j)
{
    return sp[i + n] - sd[i + n - 1] + h(j);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d%d", &p[i], &d[i]);
        p[i + n] = p[i];
        d[i + n] = d[i];
    }
    for (int i = 1; i <= n << 1; i ++ )
    {
        sd[i] = sd[i - 1] + d[i];
        sp[i] = sp[i - 1] + p[i];
    }

    int hh = 0, tt = -1;
    for (int i = 1; i <= n << 1; i ++ )
    {
        while (hh <= tt && i - que[hh] > n) hh ++ ;
        if (i > n)
            if (get_val1(i - n, que[hh]) >= 0)
                f[i - n] = true;
        while (hh <= tt && g(que[tt]) >= g(i)) tt -- ;
        que[ ++ tt] = i;
    }

    hh = 0, tt = -1;
    for (int i = n << 1; i; i -- )
    {
        while (hh <= tt && que[hh] - i > n) hh ++ ;
        if (i <= n)
            if (get_val2(i, que[hh]) >= 0)
                f[i] = true;
        while (hh <= tt && h(que[tt]) >= h(i)) tt -- ;
        que[ ++ tt] = i;
    }
    for (int i = 1; i <= n; i ++ )
        puts(f[i] ? "TAK" : "NIE");
    return 0;
}

18 评论


用户头像
光之战士   2024-07-24 22:58      4    踩      回复

这题的边界条件分析不是一般的恶心


用户头像
skyoung   2022-10-25 09:26      2    踩      回复

这个逆时针的分析写得真好! 终于懂了 感谢qwq


用户头像
251383120@qq.com   2025-05-21 16:02 · 甘肃         踩      回复

i - q[hh] > n 这个条件 和 后面用的 前缀和 。。。之间还存在一个关系。


用户头像
251383120@qq.com   2025-05-21 16:01 · 甘肃         踩      回复

能写出来框架, 范围边界搞不好, 还是不能AC


用户头像
251383120@qq.com   2025-05-21 16:01 · 甘肃         踩      回复

能写出来框架, 范围边界搞不好, 还是不能AC


用户头像
Chen_acwing_1024   2024-05-14 10:16         踩      回复

大佬,请问滑动窗口的范围是怎么推导出来的呢


用户头像
taez   2023-03-02 11:16         踩      回复

i+n 到 i 的加油:p[i+1]+p[i+2]+⋯+p[i+n] 不应该是到p[i+n+1]吗


用户头像
___io   2022-06-03 17:38         踩      回复

大佬,为什么模拟队列的$tt$初始值是-1不是0呢

用户头像
南岸以南南岸哀   2022-07-20 20:38         踩      回复

这里都行,如果是0默认数组q[0]=0;


用户头像
anss_   2022-02-24 00:17         踩      回复

$dalao$,我不知道是哪里出了问题,今天看了一天你的思路了,你这里定义$f[i]$是能否走到第i个加油站,那么顺时针的时候从$i$走到$j$,考虑能否走到$j$的时候应该是不算加油站$j$的油$p[j]$和距离$d[j]$吧,但是你算从i到j的总共加油是$sp[j]-sp[i-1]$,距离是$sd[j]-sd[i-1]$,但是按照不选第j个加油站的$p[j]$和$d[j]$,我改成$sp[j-1]-sp[i-1]$和$sd[j-1]-sd[i-1]$后也可以$AC$。
即将g函数改成:

LL g(int j)
{
    return sp[j - 1] - sd[j - 1];
}

这是什么情况呢,迷惑求解😭😭😭😭😭😭

用户头像
一只野生彩色铅笔   2022-02-24 07:40         踩      回复

我这里定义的是从 $i$ 走到 $j+1$,所以是 $sp[j] - sp[i - 1]$,你可以看到我 $j$ 的范围最大是 $i+n-1$,所以最长是恰好走一圈到 $i+n$

如果你定义的是从 $i$ 走到 $j$,滑动窗口的最大宽度也需要修正

能AC可能是数据弱了

用户头像
wellerency   2022-06-29 17:44    回复了 一只野生彩色铅笔 的评论         踩      回复

大佬,想请教一下,为什么逆时针的表达式不是sp [ i + n ] - sp [ j ] - ( sd [ i + n - 1 ] - sd [ j - 1 ]呢。我的理解是逆时针是从 i+n 走到 j 的情况,那么得到的油是从 i+n 到 j + 1,即sp [ i + n ] - sp [ j ] ,消耗的油是从i+n到j的,即 sd [ i + n - 1 ] - sd [ j - 1 ],这里的j-1是因为sd [ j - 1 ] 表示从0到j消耗的油量的总和。求解

用户头像
你是大爷妈   2022-08-08 23:31    回复了 wellerency 的评论         踩      回复

同问啊,我也是遇到这个情况了,而且按照大佬的思路逆时针的加油量不应该是sp[n+i]-sp[j]吗,但是大佬写的是sp[n+i]-sp[j-1],但是这样的话就会算上j那个点的油量,但是不应该加上j点的油量呀

用户头像
你是大爷妈   2022-08-09 00:03    回复了 你是大爷妈 的评论         踩      回复

按照我这样的理解也是可以过的,不过要先跟新后计算是否可旅行的判断,因为我们新加的点i是可以取到的,所以其跟新的队列要先计算,不然就会没有该点的判断,彩佬取j-2就会把j-1的情况算进队列里,所以可以先计算旅行判断,我的理解是这样,还望大佬指正


用户头像
自律   2022-01-10 14:34         踩      回复

大佬 状态计算提出 常量 那点看不懂
为什么提出的那一堆是常量呢

用户头像
一只野生彩色铅笔   2022-01-10 14:37         踩      回复

计算 $i$ 的状态,$i$ 是定值,当然是常量

用户头像
自律   2022-01-10 15:26    回复了 一只野生彩色铅笔 的评论         踩      回复

好吧 我还是不太理解为什么min()中被提出来的一堆是常量
我再想想吧

用户头像
自律   2022-01-10 15:32    回复了 一只野生彩色铅笔 的评论         踩      回复

懂了 是我傻了


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

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