题目
思考
默认从 x 小的走向 x 大的,因为起点终点反一下不影响。首先感觉可以模拟:能沿 x 轴往下走就往下,不能往下就左右调整到一个最近的缺口处(就是下一层(层指的是 x 轴)的 L i L_i Li 或 U i U_i Ui)。但是问题这样做在 x 轴方向上每次只能走一格,最终复杂度会是 O ( q n ) \mathcal{O(qn)} O(qn)。
然后我的想法是将向下走分成两个部分:一、沿着一个单调向右下或左下的坡走;二、没有“一”中的坡,向下飞跃。如左图。但是我们容易想到如右图的情况仍能把我们的做法卡掉。所以我想 dp:就是从上往下扫到第 j j j 层,维护 d p ( i , 0 / 1 , 0 / 1 ) dp(i,0/1,0/1) dp(i,0/1,0/1) 表示从第 i i i 层的左/右端点到第 j j j 层左/右端点的距离。然后考虑到 d p dp dp 可能在一些区间内具有同样的变化方式,所以想用线段树维护 d p dp dp。但是这也是问题所在,确实 d p dp dp 在一些区间内具有同样的变化方式,但是可能的情况是在许多极小的区间都变化不同,而且可能中间状态不能被另一个 d p dp dp 状态表示,导致 d p dp dp 转移极其复杂,反正我没写出来,大概是不可做的吧。
然后了解了一下别人的做法,似乎都和我想的不太一样,但是我看到了倍增,这启发了我。我们可以设计 d p ( i , 0 / 1 , k ) dp(i,0/1,k) dp(i,0/1,k) 表示从 i i i 层左/右端点往下走 2 k 2^k 2k 次的位置和距离。注意,我们走一次并非一层,因为向下飞跃(上一段所述向下走的第二种部分)会得到中间并非左右端点的状态,所以这里压缩掉,直接到最下然后再走斜坡。快速向下走的终点可以用单调栈加二分寻找并维护,进一步,没有向下飞跃的走法也可以包含在这之中。期望复杂度 O ( ( n + q ) log n ) \mathcal{O((n+q)\log n)} O((n+q)logn)。
解法
我们形式化地讲一下:
调整使起点层数小于终点。从后往前遍历层数
i
i
i。
向下走一次指的是先向下走到底,然后再左右调整到再下一层的区间左右端点(
L
,
U
L,U
L,U)上。这可以用左侧
L
L
L 递减以及右侧
U
U
U 递增的单调栈和二分维护并查找。
设计
d
p
(
i
,
0
/
1
,
k
)
dp(i,0/1,k)
dp(i,0/1,k) 表示从
i
i
i 层左/右端点向下走
2
k
2^k
2k 次的层数,
f
(
i
,
0
/
1
,
k
)
f(i,0/1,k)
f(i,0/1,k) 表示从
i
i
i 层左/右端点向下走
2
k
2^k
2k 次在那一层的左(
L
L
L)还是右(
U
U
U)端点(
f
f
f 在转移中需要用到),
g
(
i
,
0
/
1
,
k
)
g(i,0/1,k)
g(i,0/1,k) 表示从
i
i
i 层左/右端点向下走
2
k
2^k
2k 次的左右水平方向总距离(路程)(这里不算垂直方向,因为最后一起算
∣
s
x
−
t
x
∣
|s_x-t_x|
∣sx−tx∣ 简单)。倍增维护。
然后计算起点在
i
i
i 层的询问:一、将起点向下走一次,这样使我们得到在
d
p
dp
dp 中存在的状态。(注意特判向下走一次就超过终点的情况)二、倍增到层数
≤
\le
≤ 终点层数。三、再加上由“二”得到的最终状态到终点的距离。(可以直接减因为终点与该状态同层或在该状态向下飞跃的区间中)
评价
我为什么没想到倍增,差点就切 abc f 题了?为什么别人的做法都是线段树并且在线(纯看不懂)?