【luogu P7599】雨林跳跃
雨林跳跃
题目大意:luogu P7599
题目大意
有一排数,它们互不相同。
我们将移动定义为向左或向右走到第一个大于它的数。
现在多组询问,每次给出起点范围和终点范围,要你选起点终点,然后能走到而且用的步数最少。
如果无论如何都走不到终点就输出 -1。
思路
小性质
首先我们要发现一些性质:
是不会有跳到终点区间右边再跳回来的情况。因为终点在起点右边,那你要跳到终点右边,你跳到的位置一定会大于你起点到当前点的值,那你这个值已经比答案区间的所有值都高了,就跳不回去了。
那你就想到有往左走和往高走两个选项。
一个是为了走到终点,一个是为了更快的走到要的高度。
终点
我们设 \(M\) 是 \(BC\) 区间最高的,\(N\) 是 \(CD\) 区间最高的。
那最后一步一定是这样的:
从一个高于等于 \(M\) 小于 \(N\) 的地方向右跳,而且要 \(E\) 是 \(EM\) 区间最高的。
那这样跳就会跳入 \(CN\) 区间,就在答案区间中,我们把这些 \(E\) 叫做准终点。
那其实问题可以是如何最快走到准终点,那终点也确定了,就根据高度的限定。
确定起点
那接着我们开始想一些问题,起点选哪个?
先说说结果,如果有准终点就它,否则选 \(AB\) 中最高的 \(S\) 使得 \(SM\) 中除了 \(M\) \(S\) 最高,当然它要小于 \(N\)。
为什么呢?有准终点就很显然,没有的我们画图看看:
(\(P\) 是选的,\(R,Q\) 分别表示它左跳右跳跳到的位置)
(\(Q\) 一定不在 \(AB\) 段,否则它才是 \(P\))
由于 \(AB\) 间没有准节点,所以 \(R\) 会比 \(N\) 高。(如果不比它搞那它就应该是 \(P\))所以 \(AR\) 间都不能做起点。而如果 \(R\) 跑到 \(A\) 左边,虽然不一定比 \(N\) 高,但它就属于后面证好的 \(RP\) 中,不需要证了。
\(RP\) 之间由于 \(P\) 跳到 \(R\) 有都低于 \(P\),所以要跳出去一定会碰到 \(R,P\) 其中一个,所以不优。
\(PQ\) 之间同样的道理,但由于 \(Q\) 不在 \(AB\) 中,那 \(P\) 必然一次到 \(Q\),故别的不会比 \(P\) 更优。
那就从 \(P\) 最优。
那接着说说怎么找到,那容易看到就是从 \(B\) 开始不停往左跳,如果跳到的下一个在 \(A\) 左边或大于 \(N\) 就不跳停下。
跳的策略
那接着就是要怎么跳了。
那如果两边都能跳,而且跳了都不会大于等于 \(M\),那肯定选高的跳,加快速度。
那如果跳高的发现大于等于 \(M\),那就判断一下这样是不是在准终点,如果是就走。
否则不走,然后一直往右跳,直到跳到准终点。(当然如果跳不到就是无解)
怎么跳
容易想到倍增,就倍增左跳,右跳,高跳。
然后一开始求 \(M,N\) 是求区间最大值,可以用 ST 表来搞。(当然也要倍增)
代码
#include <vector>
#include<cstdio>
#include<iostream>
#define INF 0x3f3f3f3f3f3f3f3f
int n, h[200001], high[200001][21], left[200001][21], righ[200001][21];
int l[200001], r[200001], sta[200001], log2[200001];
int maxn[200001][21];
void init(int N, std::vector<int> H) {
n = N;
h[0] = INF;
for (int i = 0; i < N; i++)
h[i + 1] = H[i];
for (int i = 1; i <= n; i++) {//单调队列求左跳右跳跳到的位置
while (sta[0] && h[sta[sta[0]]] < h[i]) sta[0]--;
l[i] = sta[sta[0]];
sta[++sta[0]] = i;
}
sta[0] = 0;
for (int i = n; i >= 1; i--) {
while (sta[0] && h[sta[sta[0]]] < h[i]) sta[0]--;
r[i] = sta[sta[0]];
sta[++sta[0]] = i;
}
for (int i = 1; i <= n; i++) {//倍增的第一层
left[i][0] = l[i]; righ[i][0] = r[i];
if (l[i] && r[i]) high[i][0] = (h[l[i]] > h[r[i]]) ? l[i] : r[i];
maxn[i][0] = i;
}
for (int i = 1; (1 << i) <= n; i++)//倍增
for (int j = 1; j <= n; j++) {
left[j][i] = left[left[j][i - 1]][i - 1];
righ[j][i] = righ[righ[j][i - 1]][i - 1];
high[j][i] = high[high[j][i - 1]][i - 1];
if (j + (1 << i) - 1 <= n)//只有它要判,因为别的不是一个一个跳的
maxn[j][i] = (h[maxn[j][i - 1]] > h[maxn[j + (1 << (i - 1))][i - 1]]) ? maxn[j][i - 1] : maxn[j + (1 << (i - 1))][i - 1];
}
log2[1] = 0;//预处理log(ST表要用)
for (int i = 2; i <= n; i++)
log2[i] = log2[i >> 1] + 1;
}
int get_maxn(int l, int r) {//ST表求区间最大值
int log = log2[r - l + 1];
return (h[maxn[l][log]] > h[maxn[r - (1 << log) + 1][log]]) ? maxn[l][log] : maxn[r - (1 << log) + 1][log];
}
int minimum_jumps(int A, int B, int C, int D) {
A++; B++; C++; D++;
int Mx = (B + 1 <= C - 1) ? h[get_maxn(B + 1, C - 1)] : 0;
int Nx = h[get_maxn(C, D)];
int now = B, ans = 0;
for (int i = 20; i >= 0; i--)//确定起点
if (left[now][i] >= A && h[left[now][i]] < Nx)
now = left[now][i];
if (Mx <= h[now] && h[now] <= Nx) return 1;
for (int i = 20; i >= 0; i--)//先向高跳
if (h[high[now][i]] < Mx) {
ans += (1 << i);
now = high[now][i];
}
if (Mx <= h[high[now][0]] && h[high[now][0]] <= Nx) return ans + 2;
for (int i = 20; i >= 0; i--)//往左跳
if (h[righ[now][i]] < Mx) {
ans += (1 << i);
now = righ[now][i];
}
if (Mx <= h[r[now]] && h[r[now]] <= Nx) return ans + 2;
return -1;//跳不到
}