这份代码实现的是 在线 RMQ(区间最小值查询)/ LCA(最近公共祖先)算法,具体逻辑如下:
node
:表示树的节点,包含值 val
、深度 dep
、DFS 序编号 dfn
、子树范围 end
以及左右儿子指针。A[]
:Euler 序列数组,用于存储 DFS 遍历过程中访问的节点。Min[][]
:ST 表(稀疏表),用于块间 RMQ 查询。Dif[]、Pos[]
:用于块内 RMQ 的预处理。A[]
。x->dep < y->dep
。b ≈ log2(t)/2
。Min[][]
,用于块间 RMQ。Dif[]
存储块内相邻元素比较结果的差分。Pos[]
存储状态对应的最小值位置。Pos[]
的预处理结果快速求区间最小值。small_query
small_query
,中间完整块用 ST_query
,再取最小值。(T[l].dfn, T[r].dfn)
,输出最小值节点的 val
。代码详解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | #include <iostream> #include <cmath> using namespace std; /* 整体思路(LCA/RMQ 一体化): 1) 由原数组值构造 Cartesian 树(保持中序为原序、堆序为值最大堆)。 2) 对树做 Euler Tour(每到一个点入栈一次,回溯再入一次),得到序列 A[0..t-1]。 —— LCA(u,v) 即 Euler 序中区间 [dfn[u], dfn[v]] 的“最小深度”节点。 3) 对 Euler 序做“分块 + 稀疏表(ST)”: - 块内(同一块)用位运算编码相邻深度上/下走向,预处理出所有子区间的最小位置(Pos)。 - 块间(整块)用 ST 表做 RMQ。 查询: 若 l、r 在同一块 → small_query 若跨块 → 左残块 + 右残块 small_query,中间整块 ST_query,再取深度更小者 */ const int MAXN = 100000; // 原数组最大长度 const int MAXT = MAXN << 1; // Euler 序长度上界(每个点最多两次进入) const int MAXL = 18; // ST 表层数上界(log2(MAXC) < 18) const int MAXB = 9; // 每块最多 2^(MAXB-1) 个状态(b<=9 足够) const int MAXC = MAXT / MAXB; // 最多块数上界 struct node { int val; // 原数组值 / Cartesian 树结点权值 int dep, dfn, end; // 深度、在 Euler 序中的首次出现位置 dfn、子树区间右端 end node *son[2]; // 左右儿子指针:son[0]=left, son[1]=right } T[MAXN]; int n, t; // n=节点数;t=Euler 序长度 int b, c; // b=块大小;c=块数(t/b) int Log2[MAXC + 1]; // 预处理 log2 int Pos[(1 << (MAXB - 1)) + 5]; // 对任意“相邻差分位串”S,最小前缀位置(块内 RMQ 用) int Dif[MAXC + 1]; // 每块相邻差分位串:第 j 位为 1 表示 depth[j] < depth[j-1] node *root; // Cartesian 树根 node *A[MAXT]; // Euler 序(指针数组,指向 node) node *Min[MAXL][MAXC]; // ST 表(以块为单位,存每块的最小深度结点) // 以原数组值构造 Cartesian 树:中序为原序,堆序为“值最大堆” void build() { static node *S[MAXN + 1]; // 单调栈:严格递增(栈底小、栈顶大) int top = 0; for (int i = 0; i < n; i++) { node *p = &T[i]; // 把比当前 p 值小的全部弹出,并接到 p 的左儿子链上(保持“值最大堆”) while (top && S[top]->val < p->val) p->son[0] = S[top--]; // ① 左儿子链接为出栈元素 if (top) S[top]->son[1] = p; // ② 栈顶作为 p 的父,p 是其右儿子 S[++top] = p; // p 入栈 } root = S[1]; // 栈底即整棵 Cartesian 树的根 } // 对 Cartesian 树做 Euler Tour,A[] 记录访问序列 void DFS(node *p) { A[p->dfn = t++] = p; // 进入 p,入序列 for (int i = 0; i < 2; i++) if (p->son[i]) { p->son[i]->dep = p->dep + 1; // 子节点深度 = 父深度 + 1 DFS(p->son[i]); // 递归 A[t++] = p; // 回溯,再次把 p 放入 Euler 序 } p->end = t - 1; // 子树在 Euler 序的闭区间 [dfn, end] } // 比较两个结点的深度,返回更浅者(RMQ 的本质) node *min(node *x, node *y) { return (x->dep < y->dep) ? x : y; // ③ } // 构造“块间 RMQ”的 ST 表;并初始化每块的“块内最小” void ST_init() { // 选块大小 b:这里用 ceil(log2(t)/2),经验上块大小 ≈ logn/2 b = (int)(ceil(log2(t) / 2)); c = t / b; // 完整块个数(剩余不足一块的忽略 ST,只走 small) Log2[1] = 0; for (int i = 2; i <= c; i++) Log2[i] = Log2[i >> 1] + 1; // 每块的“块内最小”结点(以深度比较) for (int i = 0; i < c; i++) { Min[0][i] = A[i * b]; for (int j = 1; j < b; j++) Min[0][i] = min(Min[0][i], A[i * b + j]); } // ST 表:Min[k][j] 表示长度为 2^k 块,从 j 开始的块区间的最小 for (int i = 1, l = 2; l <= c; i++, l <<= 1) for (int j = 0; j + l <= c; j++) Min[i][j] = min(Min[i - 1][j], Min[i - 1][j + (l >> 1)]); } // 块内预处理:把“相邻深度比较”压成位串,再为所有位串状态预出“前缀最小位置” void small_init() { // 生成每块的相邻差分位串 Dif[i] // 第 (j-1) 位 = 1 表示 depth[j] < depth[j-1](下坡),否则为 0(上坡/持平) for (int i = 0; i <= c; i++) for (int j = 1; j < b && i * b + j < t; j++) if (A[i * b + j]->dep < A[i * b + j - 1]->dep) // ④ Dif[i] |= 1 << (j - 1); // 对每一个可能的状态 S(长度 b-1 的位串),计算其“前缀和最小值首次出现位置” // 规则:位 1 视为 -1,位 0 视为 +1;v 为前缀和;记录 v 的最小值所在 i(1..b-1) for (int S = 0; S < (1 << (b - 1)); S++) { int mx = 0, v = 0; // mx 记录“最小前缀和”;v 为当前前缀和 for (int i = 1; i < b; i++) { v += (((S >> (i - 1)) & 1) ? -1 : 1); // ⑤ 1:-1, 0:+1 if (v < mx) { mx = v; Pos[S] = i; // 位置 i 即子区间内最小深度的“右端位置” } } } } // ST 表查询:在块级区间 [l, r](闭区间,单位=块)中找最小深度结点 node *ST_query(int l, int r) { int g = Log2[r - l + 1]; return min(Min[g][l], Min[g][r - (1 << g) + 1]); } // 同块内的 RMQ 查询:利用 Dif[p] 的位串子段快速确定最小位置 node *small_query(int l, int r) { int p = l / b; // 所在块编号 // 取该块的位串中,对应 [l, r] 子段的状态: // 向右移去掉左边 l 之前的位,再截取长度 (r-l) 的低位。 int S = (Dif[p] >> (l - p * b)) & ((1 << (r - l)) - 1); // ⑥ // Pos[S] 为从 l 开始的偏移(1..b-1),l + Pos[S] 即最小深度元素下标 return A[l + Pos[S]]; } // 对 Euler 序区间 [l, r] 做 RMQ:若同块走 small,否则两端残块 small + 中间整块 ST node *query(int l, int r) { if (l > r) return query(r, l); int pl = l / b, pr = r / b; if (pl == pr) { return small_query(l, r); } else { // 左残块 [l, pl*b+b-1] 与 右残块 [pr*b, r] node *s = min(small_query(l, pl * b + b - 1), small_query(pr * b, r)); // 中间完整块 [pl+1, pr-1] 用 ST 表 if (pl + 1 <= pr - 1) s = min(s, ST_query(pl + 1, pr - 1)); return s; } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int m; cin >> n >> m; for (int i = 0; i < n; i++) cin >> T[i].val; // 1) Cartesian 树 build(); // 2) Euler 序 root->dep = 0; DFS(root); // 3) 分块 + ST 预处理 ST_init(); small_init(); // 每次把原数组位置 l、r 映射为 Euler 序位置 dfn,然后做区间 RMQ while (m--) { int l, r; // 原数组中的两个位置(1-based / 0-based 按题意) cin >> l >> r; // 这里假设 l、r 是 0-based 下标;若是 1-based,改为 T[l-1], T[r-1] cout << query(T[l].dfn, T[r].dfn)->val << '\n'; } return 0; } |