Loading

【题解】UOJ Long Round #2

// created on 22.09.19

A. 后门

考虑令 \(f(n,i)\) 表示长度为 \(n\) 的序列,下标为 \(i\) 的位置被计算的次数,有:

\[f(n,i)=\sum_{d=2}^{n}f(\lfloor\frac{n-(i\bmod d)}{d}\rfloor+1,\lfloor\frac{i}{d}\rfloor) \]

状态数 \(O(n\log n)\),转移的时候相邻的 \(i\) 移动量很小 .. 但是被卡空间了(不过其实也没算时间复杂度)。

而对于 \((d_1,c_1)\)\((d_2,c_2)\),合并成为 \((d_1d_2,d_1c_2+c_1)\) 。对于一个 \(i\),因为 \(\forall d\)\(c\) 都是固定值,而对于一个 \(d\),当 \(i<d\)\(i\geq n-d\) 时,序列中剩下恰好 \(i\) 一个元素。

反之,如果 \(i\geq d\) 或者 \(i\leq n-d\) 时,序列中就还剩下至少两个元素。我们要求的就是 \(d\) 序列使得最后恰好只剩下一个元素。令 \(h_n\) 表示 \(\prod d=n\) 的方案数,枚举倒数第二步时的 \(d\),此时应该满足 \(i\geq d\)\(i< n-d\),而方案数是 \(h_d\) 。此时 \(d\) 即满足 \(d\leq \max(i,n-i-1)\)

那么最后一步假设选了 \(k\),需要满足的要求是:1. \(dk\geq \max(n-i,i+1)\) 。2. 在倒数第二步 \(d\) 决定了序列长度只剩下 \(\lfloor\frac{n-(i\bmod d)}{d}\rfloor\),因此 \(k\leq\lfloor\frac{n-(i\bmod d)}{d}\rfloor\)

于是:

\[\begin{aligned} f(n,i)&=\sum_{d=1}^{\max(i,n-i-1)}h_d\left(\lfloor\frac{n-(i\bmod d)}{d}\rfloor-\lfloor\frac{\max(n-i-1,i)}{d}\rfloor\right)\\ &=\sum_{d=1}^{\max(i,n-i-1)}h_d\left(\lfloor\frac{i}{d}\rfloor+\lfloor\frac{n-i-1}{d}\rfloor+1-\lfloor\frac{\max(n-i-1,i)}{d}\rfloor\right)\\ &=\sum_{d=1}^{\max(i,n-i-1)}h_d\left(\lfloor\frac{\min(n-i-1,i)}{d}\rfloor+1\right)\\ \end{aligned} \]

\(i:[1,n]\) 时变化量是 \(O(n\log n)\) 的。故时间复杂度为 \(O(n\log n)\)

实现细则:考虑令 \(g(n)=\sum\limits_{d}h_d\lfloor\frac{n}{d}\rfloor\)\(f(n,i)\) 可以直接用 \(g(n)\) 和前缀和表示。而考虑 \(g(n)-g(n-1)\),只会在 \(d|n\) 处产生变化,即 \(\sum\limits_{d|n}h_d\),这个值恰好为 \(2h_n\) 。于是瓶颈在于求 \(h\),直接求就是 \(O(n\log n)\)

不过我们发现 \(h\) 其实忽略实际的数,我们只需要考虑不同的指数序列。同构的指数序列对应的 \(h\) 是一样的。而不同的指数序列可以搜出来 \(4223\) 个,对每个直接求值。而对于每个 \(n\) 想找到对应的指数序列,从而实现 \(O(n)\) 的话,我们考虑如下做法:从小到大枚举质数,确定其的指数。在这之前,我们将所有指数序列插入到 Trie 中,这样确定指数后就可以直接在 Trie 上走。容易发现这样做是 \(O(n)\) 的。

总结一下,挺好的一道小清新题。\(O(n)\) 的做法也算是一个小套路(指插入到 Trie 上直接走)。从合并操作入手,确定了我们计数的对象,并且确定了计数的方式。成功对我这种搜状态的人产生了误导。

B. 跳蚤猜密码

我们发现询问 \(n^2+1\) 次可以知道 \(M_{i,j}\) 。注意到我们有 \(M^{T}/\det(A)=A^{-1}\),如果 \(A\) 可逆,就可以求得 \(M\)

于是我们考虑一个权值随机的矩阵 \(C\),每次询问都令 \(B\leftarrow B+C\),这样问出来就是 \(\det((A+C)+B)\) 。注意到 \(A+C\) 基本是可逆的,这样只需要矩阵求逆后,减去 \(C\) 就得到答案了。

那怎么将多的一次减掉呢?考虑到 \(\det(M^T)=\det(A)^{n-1}\),因此如果不求 \(M^{T}_{1,1}\) 的话,也可以用拉普拉斯展开将其解出来。

总结一下,挺好的一道小清新题。主要突破口就在对余子矩阵,以及余子矩阵的一系列与原矩阵关系的公式。对于我个人而言想到余子矩阵做突破并不难 .. 但是明显我对其相关的公式掌握不够熟练,将其联系起来的水平还有待提升。

C. 霸占排行榜

\(l\) 是 Trie 大小,\(n\) 是串数,\(m\) 是拼接段数,\(w\) 是字符集大小,\(S\) 是串长总和。

首先对 Trie 建立 ACM,我们要做的就是用 \(T\) 在上面,边跑边打标记,最后求子树和。

而这个做法的优化建立在我们能解决这个问题上:怎么对 \(u\) 求出 \(u\) 后接 \(S_i\) 后,去到了哪个节点。记这个为 \(dir(i,u)\) 。可以发现,匹配的过程其实是,先在 Trie 上往下走,然后跳 Fail,然后接着走 Trie 的一个过程。我们考虑记 \(end(i,u)\) 表示 \(u\)\(S_i\),在 Trie 上能走到哪个地方。

于是路径一定是,\(u\) 先到 \(end(i,u)\),然后 \(y=\mathrm{fail}(end(i,u))\) 走下一个字符的出边。我们在这个基础上找找子问题。因为跳 Fail 的过程其实就是变成了原来的后缀,分类讨论:

  • 如果 \(u\rightarrow end(i,u)\) 的串被保留:那么我们从 \(y\) 的祖先开始走,两条路径是等价的。
  • 如果 \(u\rightarrow end(i,u)\) 的串被截断:此时我们从 \(1\) 开始走,两条路径也是等价的。如果 \(1\) 走到了另外的点,那么该点一定处在 \(y\) 的祖先链和 \(end(i,u)\) 的祖先链之中,这不符合 Fail 的定义。

而在两种情况下,我们都将起点 \(u\) 等价移动到了一个深度比 \(u\) 浅的点。可以直接 BFS 一遍求出。然后接下来要解决的就是打标记的问题,考虑我们上述过程,不难发现所有贡献的移动都可以通过打树上差分标记解决。

于是现在的问题在于求解 \(end(i,u)\),这里介绍两种方法:

  1. 考虑 重链剖分(目的在于利用轻儿子子树大小和不超过 \(O(l\log l)\) 的性质),每条重链先用 exkmp 求答案。然后我们要处理的就是在轻儿子上往下走一段 \(S_i\) 的后缀,这样的询问有 \(O(L)\) 个。

    我们考虑对 \(S\) 的后缀进行 后缀排序,那么按照顺序考虑后缀的话,其实就是按照 DFN 序考虑!也即,在处理完排名较小的后缀,只需要跳祖先直到跳到下一个询问后缀的 LCA 处,再往下走。此时的复杂度分析很简单:每个点只会经过 \(O(1)\) 次,因此复杂度是 \(O(l\log l)\)

    总结一下,重链剖分是常用的树上路径问题处理手段,而重链剖分在这种拼接路径上的作用就是:将问题分成两部分,一部分在重链上批量处理(总量 \(O(n)\)),另一部分在轻子树批量处理(总量 \(O(n\log n)\))。接着我们发现问题变成了子树内部走后缀的问题,而此时 SA 的 LCP 的性质,和树上的 LCA 直接对应了!因此当遇到树上、后缀等关键词时,可以联系 SA 的优越性质,使得问题处理得到子树大小的优秀复杂度。

  2. 这应该是在 NOI2022 冲刺期间一个叫 "诗" 的考试题中出现过:我们考虑走到了 \(u\),并且维护出 \(u\) 往上走的最长串,使得这个串的反串是 \(S_i\) 的前缀 \(S_i[1,j]\)

    每次在 \(u\) 后面加字符,就需要找到 \(S_i[1,j]\) 的 Border,使得这个 Border 在 \(S_i\) 中有对应出边。而一个 Border 如果没有对应出边的话,假设这个 Border 的长度为 \(k\),那么 \(u\)\(k\) 级祖先 \(v\) —— 此时 \(v\rightarrow u\) 正好对应这个 Border,而没有出边意味着串不会再被延长,\(end(i,v)\) 得到确定。

    考虑对于每个可能的出边,记录最长的 Border —— 因为 Border 呈 \(O(\log n)\) 段等差数列,因此需要记录的只有 \(O(\log n)\) 个位置。每次我们在 \(u\) 时只需要顺序扫描这些出边,并且触发以下事件中的一个:

    • Trie 也有该出边,那么 DFS 走到新的点。
    • Trie 没有该出边,那么至少一个 \(end(i,v)\) 被确定。

    因为两个事件的触发次数都是 \(L\),因此复杂度是 \(O(L)\) 。事实上,我们只需要记录 \(O(\log n)\) 段周期就可以避免复杂的维护。而记录周期可以直接记录上一次转移相等的位置。

    总结一下,这个做法的基本思路是我们将问题反过来,考虑贡献条件 —— 发现和 Border 相关,于是进行一个(其实比较简单的)做法设计,并分析复杂度得到正确的结论。

总结一下,整个题目的突破点在于转化问题为求 \(end(i,u)\) 。解决问题更偏向于套路积累,而转化问题则需要灵活的思路。建 ACM 是常规操作,关键在于怎么分段 \(T\) —— 我们拆解走串的过程,对第一部分进行分类讨论,得到相应的结论。所以本质还是讨论开头 / 结尾 / 合并,缩减问题规模。

提交记录:Submission #584281 - Universal Online Judge (uoj.ac)

D. 全是 AI 的比赛

不做提答。

E. 哈希杀手

给定一个 \(n-1\) 次多项式在 \(1,q,q^{2},\cdots,q^{n-1}\) 处的点值。满足这些点值只有 \(k\) 个位置非 \(0\) 。求还原出 \(m\) 项系数的值。

数据范围:模数 \(998244353\)\(n\leq p-1,m\leq n-1,k\leq \min(10^5,n)\)

好像是整式递推,算了不学了。

F. Picks loves segment tree IX

核心思想还是划分问题。

首先分析一下特殊操作:考虑一个 \(+1\) 操作什么时候会对第 \(L\) 位产生影响:只有在 \([0,L]\) 全部都是 \(1\) 时。此时如果考虑 \(1\) 的个数的话,还需要考虑操作是否是 \(+1\) 。我们转换思路,考虑按照低位 \(0\) 的个数划分状态。

按照 \(0\) 的个数划分状态,有什么好处?假设现在在做第 \(i\) 次操作浅,\([0,L]\) 上都是 \(0\) 。此时记 \(f_{L,i}\) 表示在做完第 \(f_{L,i}-1\) 次操作后,\([0,L]\) 位上的值第一次回到了 \(0\) 。于是对于区间询问,假设最开始 \([0,L]\) 上都是 \(0\),就可以从 \(l\) 开始跳 \(f_{L,i}\),直到跳出区间。而对于跳出区间前最后一个 \(i\),一直做到 \(r\),都不会发生 \([0,L]\) 上都是 \(0\) 的情况 —— 而 \(+1\) 操作影响 \(L\) 时必然造成这种情况 —— 这意味着这一段内完全可以忽略 \(+1\) 操作的影响!

而对于忽略 \(+1\) 操作的影响后的问题是平凡的,这不禁让我们感叹用 \(f\) 划分的精妙之处 —— 按照一般的思想,我们只会用 \(+1\) 操作划分操作序列,进一步地,我们考虑只用产生具体影响的 \(+1\) 操作划分(虽然低位的变化难以讨论,不过我们整个问题的转移却 并不一定受到低位的约束,而是通过拥有同样优美性质的状态转移过来!),然后,我们考虑 \(+1\) 产生的具体影响,发现 \([0,L]\) 清空时,有明显的子问题形式!

那么什么时候 \([0,L]\) 上都是 \(0\) 呢?考虑设计一个辅助数组。考虑记 \(g_{L,i}\) 表示最开始 \([0,L-1]\) 都是 \(0\),而 \(L\)\(1\),在做完 \([i,g_{L,i}-1]\) 的操作后,\([0,L]\) 第一次全部归零。

注意到 \(g_{L,i}\) 其实用来描述了一个最低 \(1\) 位上升的过程,我们可以暴力跳,直到 \([0,L]\) 全部归零(此时,从左端点开始到这个时间节点之间,\(L\) 也是不被影响的)。如果在这个时间段内跳出了区间,则意味着 \(+1\) 操作永远不会影响 \(L\)

\(g,f\) 可以通过简单的递推转移完成。对于 \(f\) 的跳跃,我们考虑树链剖分,空间复杂度做到 \(O(n\log v)\)

总结一下,我们一切后续思考的起源都是考虑 \(+1\) 操作的影响,同时我们能发现其中的单调性:在冲破 \(i\) 时,必然已经冲破了 \(i+1\) 。而在冲破 \(L\) 之前 \(L\) 都不受影响(由 \(g\) 处理),在冲破 \(L\) 之后起点变成了 \([0,L]\) 的形式,起点统一,剩下的过程仍然考虑以冲破 \(L\) 的时间做划分,每一段都不影响最终答案。

提交记录:Submission #584393 - Universal Online Judge (uoj.ac)

posted @ 2022-09-20 21:46  Qiuly  阅读(133)  评论(0编辑  收藏  举报