AT_abc417_g 题解

· · 题解

一种学数据结构学傻了的做法。

题意

定义长度为 Q+1 的字符串序列 S

### 题解 考虑如下 $O(Q^2)$ 做法: - 若要查询第 $i$ 个字符串($S_i$)中第 $x$ 个字符($(S_i)_x$),执行以下操作: - 如果 $i=0,1$,$O(1)$ 得到答案。 - 如果 $x\le|S_{L_i}|$,那么 $(S_i)_x$ 这个字符一定是来源于字符串 $S_{L_i}$,因此问题即可转化为求解 $(S_{L_i})_x$。 - 如果 $x>|S_{L_i}|$,那么 $(S_i)_x$ 这个字符一定是来源于字符串 $S_{R_i}$,因此问题即可转化为求解 $(S_{R_i})_{x-|L_i|}$。 $|S_i|$ 是可以简单处理的,因为 $x\le 10^{18}$,所以无需高精度,把 $|S_i|$ 对 $10^{18}$ 取 $\min$ 即可。 考虑离线处理,在读入完后,就把询问挂在对应的节点上,然后枚举 $Q\to 1$,把当前节点挂的询问做一个批量转移,转移就两种,要么转移到 $L_i$,要么转移到 $R_i$ 并且询问的位置整体减去 $|S_{L_i}|$。 而这个数据结构要做的就是把第 $i$ 个集合按照给定的值分裂成两个集合 $l,r$,把 $l$ 集合合并到一个集合($L_i$ 对应的集合),把 $r$ 集合每一个元素减 $|S_{L_i}|$ 合并到另一个集合($R_i$ 对应的集合)。 合并完后,编号为 $0$ 的集合中所有的元素对应的答案字符就是 $\texttt 0$,编号为 $1$ 的集合中所有的元素对应的答案字符就是 $\texttt 1$。 可以使用平衡树解决此问题,然而因为询问的 $X_i$ 是任意的,会很难处理合并的两个平衡树值域有交的情况,所以使用启发式合并或者归并即可。 具体实现可以参考[这篇题解](https://www.luogu.com.cn/article/p4ejw9j6),因为是平衡树模板之一,故不赘述,时间复杂度两只 $\log$。 ### 代码 此处展示核心代码: ```cpp int n, l[500005], r[500005], x[500005], sz[500005], rt[500005]; void solve() { cin >> n; sz[1] = 1; sz[2] = 1; for (int i = 3; i <= n + 2; ++i) { cin >> l[i] >> r[i] >> x[i]; l[i] += 1; // 下标整体加一防止程序异常对 0 号节点处理 r[i] += 1; sz[i] = sz[l[i]] + sz[r[i]]; sz[i] = min(sz[i], 1ll << 61); // 设置 sz 上限 rt[i] = t.make(x[i]); // 创建一个值为 x[i] 的节点,在平衡树中的标号为 i-2,这样即可方便输出答案。 } for (int i = n + 2; i >= 3; --i) { int L, R; int mid = sz[l[i]]; t.split(rt[i], mid, L, R); // L 是 <= mid 的,R 是 > mid 的 t.add(R, -mid); // 整体加一个值,需要用懒标记 rt[l[i]] = t.Merge(rt[l[i]], L); // 合并两个值域可能有交集的平衡树 rt[r[i]] = t.Merge(rt[r[i]], R); } t.fill(rt[1], 0); // 把整个树所有的节点对应的 ans 设置为 0 t.fill(rt[2], 1); for (int i = 1; i <= n; ++i) cout << ans[i] << '\n'; } ``` [此处为完整代码](https://atcoder.jp/contests/abc417/submissions/68152943)。需要一定卡常。