Escape Through the Leaf(迫真)
-
Sub1:
C-B 。 -
Sub2 / 3 / 4:考虑
i \to lef_i,rig_i 连边,每次对S \in [A,B] 的点 dis 设为0 ,BFS 即可,O(nq) 。 -
Sub5:首先如果
(A,C) 存在h_x \gt h_C 那么一定无解。考虑一个贪心策略:每次在i 向高度比h_C 更低的、高度更高的那一边跳,这个显然是对的(在点i 考虑左右两个单调序列),问题是如何满足比h_C 更低。注意到,如果在i 无法向左跳,那么在之后跳到的点也一定无法向左跳,故之后一直向右跳,这部分的答案很容易用单调栈预处理。于是问题变为找出这样贪心跳的过程中第一个h_{lef} \gt h_C 的点x ,并且求只保留i \to \max(lef,rig) 的边,i \to x 的距离,注意到这样连边每个点有且只有一条出边,所以连成一个森林,那么距离是容易预处理的那么可以倍增跳求距离。对于找x ,就是在祖先链上找到最深的h_{lef} \gt h_c 的点,这个也是可以倍增做到O(q \log n) 的。 -
Sub6:正着做就会有
O(n) 个x ,所以只能倒着做(,连边变为i \to \min(lef,rig) ,并且这里如果i 能一步跳到y \in [l,r] 那么就不会贪心地往另一边更低的走,那找终点x 就会变成祖先链上最深的满足h_{rig} \lt \min_{i=A}^{B} h_i 或lef \in [A,B] 的点(注意rig \in [A,B] 是不可能作为最后一步的起点的),你可以树剖之后对每条重链建动态开点线段树,维护的是区间\max dep 以及对应的编号,那么这个也是可以O(q \log n) 的,同理上面那个 Subtask 的倍增也是可以换成树剖 + 线段树二分的。
但是这两个 Subtask 的做法都强依赖于固定了一边的端点(
所以虽然貌似有 81 pts 了但是好像跟正解没什么关系(
草(,关系很大。
起点一定是 不知道为什么有人看不出来)。
所以倒过来使用 Sub6 的做法就做完了。于是你有了一个时间 感觉不是很好写 好写阿,很好写阿。
事实上你 Sub6 的做法也可以倍增 /yun
这里就考虑从
找性质应该找全(
注意边界(nxt / rig[0 ~ 17][n+1]),WA 了一万发(
constexpr int Q = 200003, oo = 2e9;
int n, Log2[Q], a[Q], seq[Q];
__attribute__((constructor)) static void
___________(){ for(int i = 2; i < Q; ++i) Log2[i] = Log2[i>>1] + 1; }
template<int N> struct MaxList {
static const int LOG = 31-__builtin_clz(N); int st[LOG+1][N];
inline int MAX(int i, int j){ return a[i] > a[j] ? i : j; }
inline void Make(int n, int *a){ copy(a+1, a+n+1, st[0]+1); for(int j = 1; j <= Log2[n]; ++j) for(int i = n-(1<<j-1); i >= 1; --i) st[j][i] = MAX(st[j-1][i], st[j-1][i+(1<<j-1)]); }
inline int Max(int l, int r){ int K = Log2[r-l+1]; return MAX(st[K][l], st[K][r-(1<<K)+1]); }
}; MaxList<Q> st; int L[Q], R[Q], nxt[18][Q], rig[18][Q], sta[Q], top;
void init(int N, vector<int> H) {
n = N; for(int i = 1; i <= n; ++i) seq[i] = i;
copy(begin(H), end(H), a+1), st.Make(n, seq);
sta[top = 1] = 0, a[0] = +oo;
for(int i = 1; i <= n; sta[++top] = i, ++i)
for(L[i] = sta[top]; a[sta[top]] <= a[i]; L[i] = sta[--top]);
sta[top = 1] = n+1, a[n+1] = +oo;
for(int i = n; i >= 1; sta[++top] = i, --i)
for(R[i] = sta[top]; a[sta[top]] <= a[i]; R[i] = sta[--top]);
for(int i = 1; i <= n; ++i)
rig[0][i] = R[i], nxt[0][i] = a[L[i]] > a[R[i]] ? L[i] : R[i];
rig[0][n+1] = nxt[0][n+1] = n+1;
for(int j = 1; j < 18; ++j)
for(int i = 1; i <= n+1; ++i)
nxt[j][i] = nxt[j-1][nxt[j-1][i]], rig[j][i] = rig[j-1][rig[j-1][i]];
// fprintf(stderr, "!!!! ");
// for(int i = n; i >= 1; --i) fprintf(stderr, "(%d , %d)%c", R[i], dep[i], " \n"[i == 1]);
}
int minimum_jumps(int A, int B, int C, int D) {
// fprintf(stderr, "sol [%d , %d] , [%d , %d]\n", A, B, C, D);
++A, ++B, ++C, ++D;
int res = 0, ptr = 0, pos = st.Max(C, D);
int Upd1 = a[st.Max(B, C-1)], Upd2 = a[pos];
int l = A, r = B, mid, ans = B+1;
while(l <= r) mid = l+r>>1, a[st.Max(mid, B)] < Upd2 ? (ans = mid, r = mid-1) : (l = mid+1);
if(ans > B || Upd1 > Upd2) return -1; ptr = st.Max(ans, B);
for(int j = 17; j >= 0; --j)
if(a[nxt[j][ptr]] <= Upd1) res += 1<<j, ptr = nxt[j][ptr];
if(ptr >= C && ptr <= D) return res; // 已经在区间内
if(R[ptr] >= C && R[ptr] <= D) return res + 1; // 跳一步
if(R[nxt[0][ptr]] >= C && R[nxt[0][ptr]] <= D) return res + 2; // 跳两步
for(int j = 17; j >= 0; --j)
if(rig[j][ptr] < C) res += 1<<j, ptr = rig[j][ptr]; // 跳祖先
return res + 1;
}