CF1913D. Array Collapse

· · 题解

D. Array Collapse

考虑分治。

f[l, r],为区间 [l, r] 能够通过操作得到的子序列数。

[l, r] 内最小元素的下标为 p

如果只考虑 [l, r] 内部的所有情况,因为无论怎么操作 a[p] 都无法被删除,所以有 f[l][r] = f[l][p - 1] \cdot f[p + 1][r]

考虑外部元素对 f[l][r] 的贡献。

显然,任意一个比 a[p] 大的数都不能使 [l,r] 内有新的子序列出现。

如果 \exists i < la[i] < a[p],那么我们可以把 [l,p] 删掉,剩下 [p + 1, r],新贡献为 f[p + 1][r]

如果 \exists j > ra[j] < a[p],那么我们可以把 [p,r] 删掉,剩下 [l, p - 1],新贡献为 f[l][p - 1]

如果上述 i,j 都存在,我们发现两者重复计算了 [l,r] 全部删掉的方案,则 f[l][r] 减一。

考虑递归边界。

如果 l > r,只存在空集这一种方案。

如果 l = r 且左右两边至少有一个小于 a[l] 的元素,则有 \phia[l] 两种,否则只有 a[l] 一种。

code

其中 f[i][j] 存的是 [i, i + 2^j - 1] 内最小元素的位置。

```c++ void init() { for(int i = 1; i <= n; ++ i) f[i][0] = i; for(int j = 1; j < 20; ++ j) { for(int i = 1; i + (1 << j) - 1 <= n; ++ i) { if(a[f[i][j - 1]] < a[f[i + (1 << j - 1)][j - 1]]) f[i][j] = f[i][j - 1]; else f[i][j] = f[i + (1 << j - 1)][j - 1]; } } } int get_pos(int l, int r) { int len = log2l(r - l + 1); if(a[f[l][len]] < a[f[r - (1 << len) + 1][len]]) return f[l][len]; else return f[r - (1 << len) + 1][len]; } ll get_val(int l, int r, bool L_less, bool R_less) { if(l > r) return 1; if(l == r && (L_less || R_less)) return 2; if(l == r) return 1; int p = get_pos(l, r); ll left = get_val(l, p - 1, L_less, true); ll right = get_val(p + 1, r, true, R_less); ll ret = left * right % P; if(L_less) ret += right; if(R_less) ret += left; if(L_less && R_less) -- ret; return (ret % P + P) % P; } void solve() { cin >> n; for(int i = 1; i <= n; ++ i) cin >> a[i]; init(); cout << get_val(1, n, 0, 0) << '\n'; } ```