P14528 [BYOI R1] 雨后屋檐 题解

· · 题解

:::info[题面 & 提示]

P1318 加强版,区别在于本题包含多个询问,每个询问给定查询的区间 [l, r] 与最大水面高度 H,强制在线。P1318 的做法对本题有一些启发。

:::

n, q 同阶。

考虑本题的难点在哪里。发现屋顶的两侧不一定足够高,所以一些水会流出去。

考虑特殊性质,此时没有水会流出去。考虑这时该怎么做。发现此时的答案为:

\sum_{i = l}^r \max(H - h_i, 0)

即:

\sum_{l \le i \le r, h_i \le H} H - h_i

发现这个问题相当于二维数点,于是考虑使用可持久化权值线段树解决,详见 P10814。

去掉特殊性质,容易发现,只要一个区间两侧的屋顶高度 \ge H,就可以用上述方法解决。于是我们尝试从询问的区间 [l, r] 中找到一个两端高度都不小于 H 的子区间。

H \leftarrow \min(\max_{i = l}^r \{h_i\}, H),这对答案没有影响;令 L 表示区间左侧第一个满足 h_i \ge H 的位置 i;同理,令 R 表示区间右侧第一个满足 h_i \ge H 的位置 i。计算 L, R 可以通过线段树二分解决。显然 L, R 一定存在。

此时问题被划分为了 [l, L)[L, R](R, r] 三段。第二段是前文中已经讨论的情况,是容易算出水的体积的。一三段的问题是对称的,所以接下来讨论第一段。

接下来是 P1318 的结论。

设位置 i 的水面相对地面高度为 a_i。特别地,若位置 i 没有水,则令 a_i = h_i。容易注意到,\forall i \in [l, L),有:

a_i = \min(\max_{j = l}^i\{h_j\}, H)

注意到前者一定小于后者,于是我们就有了第一段的答案:

\sum_{i = l}^{L - 1} \max_{j = l}^i \{h_j\} - \sum_{i = l}^{L - 1} h_i

后者是容易维护的,考虑如何维护前者。前者是区间前缀最大值之和,即 P5648。直接使用 P5648 的做法即可。

直接把三段的答案加在一起就可以得到总答案。

时间复杂度 O(n \log n),空间复杂度 O(n \log n),有巨大常数。

:::info[一个常数优化]{open}

考虑优化计算 L 的过程。

按照 P5648 的做法,我们利用单调栈建了一棵树。注意到在向右连边的树中,L 一定是 l 的父亲或其本身,所以可以通过树上倍增来计算 L

::: :::success[Code]{open} 我人傻常数大,跑得挺慢的。 ```cpp line-numbers #include <bits/stdc++.h> #define rep(i, s, e) for(int i = s; i <= e; ++i) #define _rep(i, s, e) for(int i = s; i >= e; --i) #define uint unsigned int #define uint64 unsigned long long using namespace std; const uint N = 500000; template <typename T> inline void read(T &x) { uint neg = 1; char ch; while (ch = getchar(), !isdigit(ch)) if (ch == '-') neg = -1; x = ch - '0'; while (ch = getchar(), isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ '0'); x *= neg; } template <typename T> inline void write(T x) { if (x < 0) putchar('-'), x = -x; if (x > 9) write(x / 10); putchar(x % 10 + '0'); } // 可持久化权值线段树 struct Segtree { uint rt[N + 5], ls[N << 5], rs[N << 5], cnt[N << 5], cntnode = 0; uint64 sum[N << 5]; void build(uint l, uint r, uint &p) { if (!p) p = ++cntnode; sum[p] = 0; if (l == r) return; uint mid = (l + r) >> 1; build(l, mid, ls[p]); build(mid + 1, r, rs[p]); } void insert(uint l, uint r, uint &p, uint rt, uint s, uint d) { if (!p) p = ++cntnode; sum[p] = sum[rt] + d; cnt[p] = cnt[rt] + 1; if (l == r) return; uint mid = (l + r) >> 1; if (s <= mid) { insert(l, mid, ls[p], ls[rt], s, d); rs[p] = rs[rt]; } else { insert(mid + 1, r, rs[p], rs[rt], s, d); ls[p] = ls[rt]; } } pair<uint64, uint> query(uint l, uint r, uint p, uint s, uint t) { if (s > t) return {0ull, 0}; if (s <= l && r <= t) return {sum[p], cnt[p]}; uint mid = (l + r) >> 1, ans2 = 0; uint64 ans1 = 0; pair<uint64, uint> tmp; if (s <= mid) tmp = query(l, mid, ls[p], s, t), ans1 += tmp.first, ans2 += tmp.second; if (t > mid) tmp = query(mid + 1, r, rs[p], s, t), ans1 += tmp.first, ans2 += tmp.second; return {ans1, ans2}; } } tr; uint n, q, t, h[N + 5], to[N + 5], stk[N + 5], l, r, H, L, R, tp, tmp, lfa[N + 5][20], rfa[N + 5][20]; uint64 xl, xr, xH, ans, xorans, s[N + 5], ldep[N + 5], rdep[N + 5]; pair<uint64, uint> tmp1, tmp2; signed main() { read(n), read(q), read(t); rep(i, 1, n) { read(h[i]); to[i] = h[i]; s[i] = s[i - 1] + h[i]; } sort(to + 1, to + n + 1); // 建可持久化权值线段树 tr.build(1, n, tr.rt[0]); rep(i, 1, n) { uint pos = lower_bound(to + 1, to + n + 1, h[i]) - to; tr.insert(1, n, tr.rt[i], tr.rt[i - 1], pos, h[i]); } h[0] = h[n + 1] = 1e9 + 7; // 建向右的树 tp = 0; rep(i, 1, n + 1) { while(tp and h[stk[tp]] < h[i]) { rfa[stk[tp--]][0] = i; } stk[++tp] = i; } _rep(i, n, 1) { rdep[i] = rdep[rfa[i][0]]; rdep[i] += 1ull * h[i] * (rfa[i][0] - i - 1); rdep[i] -= (s[rfa[i][0] - 1] - s[i]); } rfa[n + 1][0] = n + 1; rep(j, 1, 19) { rep(i, 1, n) { rfa[i][j] = rfa[rfa[i][j - 1]][j - 1]; } } // 建向左的树 tp = 0; _rep(i, n, 0) { while(tp and h[stk[tp]] < h[i]) { lfa[stk[tp--]][0] = i; } stk[++tp] = i; } rep(i, 1, n) { ldep[i] = ldep[lfa[i][0]]; ldep[i] += 1ull * h[i] * (i - lfa[i][0] - 1); ldep[i] -= (s[i - 1] - s[lfa[i][0]]); } lfa[0][0] = 0; rep(j, 1, 19) { rep(i, 1, n) { lfa[i][j] = lfa[lfa[i][j - 1]][j - 1]; } } while (q--) { read(xl), read(xr), read(xH); if(!t) ans = 0; l = xl ^ ans, r = xr ^ ans, H = xH ^ ans; ans = 0; if(h[L = l] < H) { // 倍增求 L _rep(i, 19, 0) { if(rfa[L][i] <= r and h[rfa[L][i]] < H) { L = rfa[L][i]; } } if(rfa[L][0] <= r) L = rfa[L][0]; } R = r; if(h[R = r] < H) { // 倍增求 R _rep(i, 19, 0) { if(lfa[R][i] >= l and h[lfa[R][i]] < H) { R = lfa[R][i]; } } if(lfa[R][0] >= l) R = lfa[R][0]; } H = min(H, max(h[L], h[R])); // 处理 [L, R] if (L + 1 <= R - 1) { tmp = upper_bound(to + 1, to + n + 1, H) - to - 1; tmp1 = tr.query(1, n, tr.rt[R - 1], 1, tmp); tmp2 = tr.query(1, n, tr.rt[L], 1, tmp); ans += 1ull * (tmp1.second - tmp2.second) * H; ans -= tmp1.first - tmp2.first; } if (l <= L) ans += rdep[l] - rdep[L]; // 处理 [l, L) if (R <= r) ans += ldep[r] - ldep[R]; // 处理 (r, R] xorans ^= ans; } write(xorans); return 0; } ``` :::