题解:P16762 [GKS 2020 #D] Locked Doors

· · 题解

题意

N 个房间排成一行,相邻房间之间有难度互不相同的门。
从房间 S 开始拍照,之后每次在当前已访问区间左右两侧的门中,选择难度较小的门打开,并进入新房间拍照。

从房间 $S$ 开始,第 $K$ 个拍照的房间是哪个? ## 题解 无论从哪个房间开始,最后一定会访问完所有房间,我们试着从这个最终状态反推。 设当前区间内难度最大的门为 $D_m$,以它为界把区间分为两部分。观察能够得到:Bangles 在打开 $D_m$ 前,一定访问完了起点 $S$ 所在部分,而另一侧会在最后被**顺序**访问。 可以直接排除不包含起点的部分,然后对包含起点的剩余区间继续寻找最大门进行分治。重复上述过程,即可找到第 $K$ 个被访问的房间。 这个结构每次都在寻找当前区间的最大值然后递归左右部分,这不是很符合大根笛卡尔树的结构吗? 具体地,对数组 $A$ 建立大根笛卡尔树,数组下标满足 BST 性质,数组元素满足大根堆性质。 为了从房间出发进行查询,我们需要在笛卡尔树中补上叶子节点来表示房间。 对于节点 $u$: - 若其没有左孩子,则房间 $u$ 作为其左叶子。 - 若其没有右孩子,则房间 $u+1$ 作为其右叶子。 这样能够不重不漏地把所有房间添加到树中。 原本的反推相当于在笛卡尔树上面由上到下,建好了树之后即可改为由下到上顺推,这样就能倍增加速。 对每个节点 $u$,预处理以下信息: - $\text{cnt}_u$:子树中的房间数。 - $L_u, R_u$:子树覆盖的房间区间边界。 - $\text{fa}_{u, j}$:节点 $u$ 的第 $2^j$ 个祖先。 我们需要从房间 $S$ 对应的叶子节点向上找到一个节点 $u$,使得从 $S$ 到 $u$ 的路径上,包含 $S$ 的那个孩子节点 $v$ 满足 $\text{cnt}_v < K$,但 $\text{cnt}_u \ge K$。 找到节点 $u$ 后: - 若 $S \le u$(起点在左侧),说明右侧房间是按从左到右顺序访问的,答案为 $L_u + K - 1$。 - 若 $S > u$(起点在右侧),说明左侧房间是按从右到左顺序访问的,答案为 $R_u - K + 1$。 时间复杂度:$\mathcal{O}(N \log N + Q \log N)$。 空间复杂度:$\mathcal{O}(N \log N)$。 ::::info[代码] ```cpp #include<bits/stdc++.h> #define lc t[u].ls #define rc t[u].rs using namespace std; constexpr int N = 1e5 + 7, M = 2e5 + 7, LOG = 20; int T, n, q; int a[M], st[N], top; int fa[LOG][M]; struct node{ int ls, rs, fa; int val, L, R; void init(){ ls = rs = fa = 0; val = L = R = 0; } }t[M]; void init(){ top = 0; for (int i = 1; i <= 2 * n; i++) t[i].init(); } void dfs(int u){ if (u == 0) return; fa[0][u] = t[u].fa; for (int i = 1; i < LOG; i++) fa[i][u] = fa[i - 1][fa[i - 1][u]]; if (u >= n){ t[u].val = 1; return; } dfs(t[u].ls), t[u].val += t[lc].val, t[u].L = t[lc].L; dfs(t[u].rs), t[u].val += t[rc].val, t[u].R = t[rc].R; } int main(){ ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> T; for (int _ = 1; _ <= T; _++){ cout << "Case #" << _ << ": "; cin >> n >> q; for (int i = 1; i < n; i++) cin >> a[i]; init(); for (int i = 1; i < n; i++){// 建树 int lst = 0; while (top && a[st[top]] < a[i]) lst = st[top--]; if (top){ t[st[top]].rs = i; t[i].fa = st[top]; } if (lst) t[lst].fa = i; t[i].ls = lst; st[++top] = i; } for (int i = 1; i < n; i++){// 补房间 if (!t[i].ls){ t[i].ls = i + n - 1; t[i + n - 1].L = t[i + n - 1].R = i; t[i + n - 1].fa = i; } if (!t[i].rs){ t[i].rs = i + n; t[i + n ].L = t[i + n].R = i + 1; t[i + n].fa = i; } } dfs(st[1]); for (int o = 1; o <= q; o++){ int s, k; cin >> s >> k; int u = n - 1 + s; for (int i = LOG - 1; i >= 0; i--){ int f = fa[i][u]; if (f && t[f].val < k) u = f; } u = fa[0][u]; if (s <= u) cout << t[u].L + k - 1 << ' '; else cout << t[u].R - k + 1 << ' '; } cout << '\n'; } } ``` ::::