最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
给定一个 环,环 上有 $n$ 个节点,编号从 $1 \sim n$,以及一辆小车车
每一个 节点 $i$ 有一个 权值 $p_i$ 表示当车 到达该点 时,可以 获得 的 油量
还有一个 权值 $d_i$ 表示当车从 节点 $i$ 到 节点 $i+1$ 所需要 消耗 的 油量
现有一辆车想从环上 任意点 出发,顺时针 或 逆时针 绕环一圈走回起点
行驶的过程中,油量不能为 负数,初始油量 为 起点 处所能获得的 油量
判断能否完成 环圈行驶
分析
看到 环形问题,就会想到在先前 环形区间DP 中提到的一个解决方法:拉环成链
复制 一份数组接在 原数组 后面,这样原问题就 等价于 在 新数组 中 存在 长度为 $n + 1$ 的 合法区间
然后考虑区间何时 合法 的问题,题中提及:
- 会先在一个点 获得油量
- 然后 消耗油量 到达下一个点
- 消耗后的油量 不能小于 0
这极像一个我们熟悉的 括号序列模型 — 任意 前缀区间 必然满足 左括号数$\ge$右括号数
故想到用 前缀和思想 来分析:该 区间 如果 合法,则必然 任意前缀 获得油量 $\ge$ 消耗油量
这里我们要 明确 每个数组下标的实际含义,才能调对本题的 边界,不然会
WA
疯
先讨论 顺时针的走法
- $d[i]$:点 $i \rightarrow$ 点 $i+1$ 消耗 的 油量
- $p[i]$:到点 $i$ 能 获得 的 油量
相对应的 前缀和 含义:
- $sd[i]~(d[1 \cdots i])$:从 $1$ 出发到 $i+1$ 所 消耗 的 总油量
- $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;
}
这题的边界条件分析不是一般的恶心
这个逆时针的分析写得真好! 终于懂了 感谢qwq
大佬,请问滑动窗口的范围是怎么推导出来的呢
i+n 到 i 的加油:p[i+1]+p[i+2]+⋯+p[i+n] 不应该是到p[i+n+1]吗
大佬,为什么模拟队列的$tt$初始值是-1不是0呢
这里都行,如果是0默认数组q[0]=0;
$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函数改成:
这是什么情况呢,迷惑求解😭😭😭😭😭😭
我这里定义的是从 $i$ 走到 $j+1$,所以是 $sp[j] - sp[i - 1]$,你可以看到我 $j$ 的范围最大是 $i+n-1$,所以最长是恰好走一圈到 $i+n$
如果你定义的是从 $i$ 走到 $j$,滑动窗口的最大宽度也需要修正
能AC可能是数据弱了
大佬,想请教一下,为什么逆时针的表达式不是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消耗的油量的总和。求解
同问啊,我也是遇到这个情况了,而且按照大佬的思路逆时针的加油量不应该是sp[n+i]-sp[j]吗,但是大佬写的是sp[n+i]-sp[j-1],但是这样的话就会算上j那个点的油量,但是不应该加上j点的油量呀
按照我这样的理解也是可以过的,不过要先跟新后计算是否可旅行的判断,因为我们新加的点i是可以取到的,所以其跟新的队列要先计算,不然就会没有该点的判断,彩佬取j-2就会把j-1的情况算进队列里,所以可以先计算旅行判断,我的理解是这样,还望大佬指正
大佬 状态计算提出 常量 那点看不懂
为什么提出的那一堆是常量呢
计算 $i$ 的状态,$i$ 是定值,当然是常量
好吧 我还是不太理解为什么
min()
中被提出来的一堆是常量我再想想吧
懂了 是我傻了