UOJ Logo EntropyIncreaser的博客

博客

标签

2020年北京青少年信息学科普日市级活动(初中组)题解

2020-11-05 13:06:37 By EntropyIncreaser

T1 生命游戏(gol)

注意到只有 $2^{4\times 4}$ 种输入,我们可以先枚举每种输入确立其下一个状态,然后倍增或者通过内向树处理都是可以的。

时间复杂度 $\Theta(states\log T + Q\log T)$ 或者 $\Theta(states + Q)$。

T2 装备升级(upgrade)

性质 A

我们容易发现这样一个事实:不可能有两个装备恰好是一级,否则一定可以将一个升级挪到另一个上。

因此可以导出这一性质:

  • 若 $m$ 是偶数,那么一定是选 $w_1 + w_2$ 前 $m/2$ 小的。

  • 若 $m$ 是奇数,那么要么是选前 $\lfloor m/2\rfloor$ 小的 $w_1+w_2$,再在剩下的里面选一个一级,要么是选前 $\lceil m/2\rceil$ 小的 $w_1+w_2$,再在这些里面退掉一个二级。

因此我们先按照 $w_1+w_2$ 排序,然后枚举就可以了。

性质 B

我们考虑贪心选取最小的 $m$ 个 $w$,不难发现他们一定是可以满足要求的。因此只需一次 $\texttt{nth_element}$,可以做到 $\Theta(n)$。

std

我们考虑 $w_1\le w_2$ 和 $w_1>w_2$ 已经涵盖了所有可能性,我们考虑对于两拨分别算出所有 $m$ 的答案,之后枚举一边实际选了几个就可以了。

对于性质 A,我们可以用前后缀 $\min$ 来加速,对于性质 B,我们只需要排序之后做一遍前缀和即可。

因此总共复杂度瓶颈为排序,$\Theta(sort(n))$。

T3 因子统计(divisor)

我们注意到如果一个数的质因子分解是 $p_1^{k_1} \cdots p_n^{k_n}$,那么因子数量就是 $(k_1+1)\cdots (k_n+1)$。

std

考虑一个莫队,我们在 $(n,m)$ 的移动过程中维护 $\binom n m$ 的质因子分解中 $> \sqrt n$ 的那部分,这样每次移动只会有 $\Theta(1)$ 个质因子增减。最后统一统计 $\le \sqrt n$ 的质因子贡献,因为总共只有 $\pi(\sqrt n) = \Theta \left( \frac{\sqrt n}{\log n}\right)$ 个质数,我们每个质数暴力做就是 $\Theta(\sqrt n)$ 的了。复杂度是 $\Theta(q\sqrt n + n\sqrt q)$。

bonus

本题可以做到……的复杂度?

BJ United Round #4 (Mirror of Haidian 2019)题解

2019-09-21 12:46:51 By EntropyIncreaser

石子移动

考虑将操作过程倒过来,最后值域在 $[l, r]$ 内的在上一步应该也是一个区间,便可得出最初的区间限制。

时间复杂度 $\Theta(n + q)$。

分数排序

$n$ 阶 Farey 数列可以直接在 stern-brocot 树上遍历:初始设置 $\frac ab = \frac 01, \frac xy = \frac 11$。那么 $(\frac ab, \frac xy)$ 这一节点的左子和右子分别为 $(\frac ab, \frac {a+x}{b+y}), (\frac{a+x}{b+y}, \frac xy)$。不难证明任何出现的一个分数的分子分母都是互质的,且这样可以遍历所有分数。

考虑如何在这个树上找到一个分数,虽然一个节点到根的距离可以是 $\Theta(n)$ 的(例如 $\frac 1n$),但是假设我们走到 $(\frac ab, \frac{a+x}{b+y})$ 叫做 $L$ 操作,否则叫做 $R$ 操作,那么一个节点对应找到它的操作序列的连续段数应该是 $\mathcal O(\log n)$ 的,因为一次 $LR$ 会使得分母倍增。我们可以倍增看操作序列一共下一组 $L$ 或者 $R$ 有多长,因此我们会进行 $\mathcal O(\log^2 n)$ 次 check 操作。

考虑如何计算一次 check: $$ \begin{eqnarray} & &\sum_{x\le n} \sum_{y\le n} \left[\frac xy \le \frac ab\right] [\gcd(x, y) = 1]\\ &= & \sum_{x\le n} \sum_{y\le n} \left[\frac xy \le \frac ab\right] \sum_{d | (x, y)} \mu(d)\\ &= & \sum_d \mu(d) \sum_{y\le \frac nd} \sum_{x\le \frac nd} \left[x \le \frac {ay}b\right] \\ &= & \sum_d \mu(d) \sum_{y = 1}^{\left\lfloor\frac nd\right\rfloor} \min\left( \left\lfloor \frac nd \right\rfloor, \left\lfloor \frac{ay}b \right\rfloor \right) \end{eqnarray} $$ 对 $\left\lfloor\frac nd\right\rfloor$ 除法分块,我们需要 $\mu(d)$ 在这些位置的前缀和,而 $\sum_{y = 1}^{\left\lfloor\frac nd\right\rfloor} \min\left( \left\lfloor \frac nd \right\rfloor, \left\lfloor \frac{ay}b \right\rfloor \right)$,这个式子在一个前缀中取得右边是较小值,这部分我们可以通过类欧几里得算法在 $\Theta(\log b)$ 时间内计算。因此我们一次 check 的时间消耗为 $\Theta(\sqrt n \log n)$。注意前缀和的处理可以使用 $\Theta(n^{\frac 23})$,但是在本题限制下 $\Theta(n)$ 也是 ok 的。

综上所述,时间复杂度为 $\Theta(n^{\frac 23}) + \Theta(\sqrt n \log^3 n)$。

彼岸蔷薇

newbiedmy 这家伙会乱搞,很有毒!

关于背景

那段歌词摘自遗忘山丘

算法一

考虑枚举所有路径,对于每个情况按照定义求出最大路径数。

显然最大路径数可以通过树形 dp 进行计算。$f(u)$ 表示考虑所有两端点均在 $u$ 的子树的路径,此时的最大答案。此时分成两类转移情况讨论,要么选择的路径均不经过 $u$,那么这一情况的最优解即 $u$ 的所有直系孩子的 $f$ 之和。否则枚举选取哪个路径,这一情况的贡献是 $1$ 加上与这条路径相邻的所有节点的 $f$ 之和。

时间复杂度 $\Theta(mn^3)$,预计得分 $32\%$。

算法二

考虑优化 dp 的效率,我们记 $g(u)$ 为 $f(u)$ 减去其所有直系孩子的 $f$,则不难发现 $g(u)$ 对于路径情况的转移就是所选路径上 $g$ 的和再加 $1$。通过一个树状数组即可维护。

时间复杂度 $\Theta((n + m\log n)n^2)$,预计得分 $40\% \sim 44\%$。

算法三

我们对于计算最大路径数有两种不同角度的观察:

  1. 显然 $g$ 的值要么为 $0$ 要么为 $1$,这表示事实上 $g$ 就是代表达到子树中最优解是否必须有一条路径穿过 $u$,这当且仅当存在一条路径经过的 $g$ 均为 $0$。
  2. 这也可以看成一个贪心,我们仅仅是按照路径的 lca 自底向上贪心,能放就放。

考虑通过这种贪心思路来计算解,我们只需在 dfs 的过程中额外维护一个 vis 数组,表示“这个节点是否上面有一个父亲已经作为某条被选择路径的 lca”。未被染色的节点显然时刻都是与根节点相连的一个连通块。我们每次选择一条路径,只需把该节点子树中与之相邻的未被染色的节点染色。检查一条路径的时候,只需检查两端点是否均未被染色。这样就可以做到线性。

时间复杂度 $\Theta((n + m)n^2)$,预计得分 $44\%$。

算法四

我们提出如下观察:假设一个节点 $u$ 满足加入路径 $(u,u)$ 能够使得最大路径数增加,则称节点 $u$ 是 ok 的。那么一条路径加入后能使最大路径数增加,当且仅当路径上的节点都是 ok 的。

之前的过程中我们对于根的选择是任意性的,接下来我们考虑以一个节点为根的时候,穿过它的路径如果能够增大最大路径数,此时显然等价于路径上的节点均未被选中,也就是在贪心过程中这条路径就可以被选。

我们考虑染色之后与根节点联通的未被染色的连通块,这应当与与 $u$ 相邻的 ok 的节点所构成的的连通块是一致的。因为我们考虑在未被染色的这个连通块内进行换根,其他子树必然没有发生结构变化所以染色情况不变,因此穿过这个连通块的所有路径均不能选,在这个情况下染色数组完全没有变化,因此这个连通块的任何一个节点都是 ok 的。

我们只需以每个点为根进行一次算法三的操作,然后对于每个连通块,对答案的贡献就是连通块大小的平方。

时间复杂度 $\Theta((n+m)n)$,预计得分 $60\%$。

算法五

我们如何快速计算一个节点是否 ok。注意到贪心选择出的每条路径的 LCA,在任何一组最大路径集合中,这些节点都必然各自被一条路径覆盖。因此我们考虑将贪心选择出的每个路径及其 LCA 作为基础,发现在 LCA 的子树中的,不经过其他 LCA 的这一连通块内部,不 ok 的节点显然是该 LCA 对应的路径的一个子段。为了注意到为了让一颗子树里的节点不被覆盖,我们可以通过更换路径使得新的不与其他路径冲突的路径,用于更新原本路径 ok 的部分。注意在此时可以牺牲上方的空间,因此这个思路必须是自顶向下更新的。

因此两类路径可以用于更换被选路径:

  1. 该路径的 LCA 是 ok 的,此时显然这一路径已经与某条路径相交,如果只与一条路径相交,则可以更新该路径。
  2. 该路径的 LCA 和某个 LCA 重合,且不与其它路径相交,此时可以更新该路径。

对于如何快速判断一条路径是否只与一条路径相交,与哪条路径相交,我们可以打一个标记分为 $0, v, -1$,表示这一节点向上被选择的 LCA 有 $0$ 个,有 $1$ 个并标记其编号,或者有 $\ge 2$ 个。这样只需通过 DFS 即可更新标记,且一个节点只会被更新 $2$ 次。

通过离线处理,我们可以做到时间复杂度 $\Theta(n + m\alpha(m,n))$,如果使用 $\Theta(n)-\Theta(1)$ LCA 技术也可以做到 $\Theta(n+m)$,预计得分 $100\%$。

BJ United Round #3 题解

2019-09-07 18:00:27 By EntropyIncreaser

三色树

这道题的主要目的是普及无标号无根树的计数方法,故不讨论非多项式时间做法。其实 $n$ 完全可以出得更大,但是没必要。

这道题改自 ProjectEuler#677

思路要点

首先考虑无标号有根树,我们只需维护 dp 数组 $R(n), B(n), Y(n)$ 即可。通过更新 dp 数组 $Q(d, n)$ 表示此时不在意节点的根的颜色,且根的度数为 $d$ 即可维护红根和蓝根,$W(d, n)$ 用于维护孩子没有黄根树的,这可以帮助计算黄根。每次 update 会消耗 $\Theta(n)$ 的时间,比如说已经计算完了 $R(n)$ 的值,那么它作为子树有可能出现了 $k$ 次,则选出 $\displaystyle \binom{R(n) + k - 1}{k - 1}$ 种方案,令 $Q(d,N)$ 加上 $\binom{R(n) + k - 1}{k - 1} Q(d - k, N - kn)$。

这一部分的复杂度为 $\Theta(n^2)$。

计算无标号无根树的要点是观察树的一个特殊点:一颗树要么存在唯一一个重心使得任何一个子树的大小均 $< \frac{n}2$,要么存在一个“重边”使得它将树恰好分为大小为 $\frac n2$ 的两部分。

对于前者部分的计数,我们只需将 dp 数组计算到 $\lceil \frac n2 \rceil - 1$ 时,将 $Q,R$ 数组取出。对于后者,若 $n$ 是偶数,当计算完 $\frac n2$ 的 dp 值时,将 $R, B, Y$ 用于计算即可。

如何做到更快

考虑计算 $R,B,Y$ 的生成函数 $G_R(x), G_B(x), G_Y(x)$。

我们记 $S = G_R + G_B + G_Y$,我们使用 Pólya 计数定理表示 $3$ 个孩子的树,通过枚举置换群: $$ \frac{S(x)^3 + 3S(x^2)S(x) + 2S(x^3)}{3!} $$

读者可自己尝试推导 $4$ 和 $2$ 个孩子的树。

由此我们可以通过 $G_R, G_B, G_Y$ 自身带入 $x, x^2, x^3$ 这些的多项式来表示。接下来考虑如何求解。

我们让 $G_R, G_B, G_Y$ 放在一起进行迭代,我们考虑倍增,由于已经计算出了前 $\frac n2$ 项,所有带入 $x^2, x^3$ 的部分就已经确定了,我们可以认为是常数。接下来就是一个关于 $G_R, G_B, G_Y$ 的三元三次方程,这依然可以通过牛顿迭代解决,也就是这等价于根据 $G_R, G_B, G_Y$ 的定义式得到了 $3$ 个它们之间的关系式 $E_R/E_B/E_Y(G_R, G_B, G_Y) = 0$,将 $E_R/E_B/E_Y(u, v, w)$ 在 $G_R, G_B, G_Y$ 处泰勒展开就是一次方程,求解一个三元一次方程即可。

本算法的复杂度为 $\Theta(n\log n)$,常数显而易见的大,欢迎有兴趣的同学来实现

押韵

事实上本题是 UOJ #450. 【集训队作业2018】复读机 的一个加强版。

算法一

$n\le 5\times 10^4$,我可以做多项式快速幂!这个多项式就是 $(\sum_{j\ge 0} \frac{x^{jd}}{(jd)!})^k$。这个模数不太友好,你可能需要 MTT……常数应该很大。

时间复杂度 $\Theta(n\log n) \sim \Theta(n\log n \log k)$,前者是进行多项式 $\ln , \exp$ 达到的复杂度,但是常数可能很大。

预计得分 $20\%$。

算法二

我们考察 $(\sum_{j\ge 0} \frac{x^{jd}}{(jd)!})^k$ 可以如何表示。

事实上与周期函数有关的,我们可以自然想到类似 DFT 的形式,也就是考虑到 $\sum_{j=0}^{d - 1} \omega_d^{cj} = d[d | c]$,因此我们可以知道上面的那个多项式实际上是

$$ \frac1d \sum_{j=0}^{d-1} \exp \omega_d^j x $$

我们直接枚举拆括号的过程中计算了几个 $\exp \omega_d^0 x$,几个 $\exp \omega_d^1 x$……然后这一部分的贡献就是 $(c_0 + c_1 \omega_d^1 + c_2 \omega_d^2 + \dots)^n$。最后总和除以 $d^k$。

时间复杂度 $\Theta(k^{d-1}\log n)$,预计得分 $50\%$。

算法三

主要思想还是围绕优化计算下式:

$$ [x^n]\left( \frac1d \sum_{j=0}^{d-1} \exp \omega_d^j x \right)^k $$

对于 $d=4$ 的情况,我们实际上是只需要统计每个 $\exp (a + b\mathrm{i})x$ 出现多少次。通过推导一些式子,或者直接将复平面旋转 $45^\circ$ 我们可以得到,方案数是 $\displaystyle\binom{k}{\frac{k + |a+b|}2} \binom{k}{\frac{k + |a-b|}2}$。因此答案可以在 $\Theta(k^2 \log n)$ 内计算出来。

预计得分 $70\%$。

算法四

对于 $d=6$ 的情况,我们注意到 $\omega_6$ 的代数关系有 $\omega - 1 = \omega^2$,因此所有的根都可以用 $1, \omega$ 表示,即给每个数赋予了一个离散的二维坐标 $$ \begin{array}{clclc} \omega^0 & = & 1 & & \\ \omega^1 & = & & & \omega\\ \omega^2 & = & -1 & + & \omega\\ \omega^3 & = & -1 & & \\ \omega^4 & = & & &-\omega\\ \omega^5 & = & 1 & + &-\omega \end{array} $$ 因此我们只需要做一个二维的快速幂,倍增 FFT 可以做到 $\Theta(k^2 \log k)$。但是常数可能较大。

预计得分 $80\% \sim 100\%$。

算法五

考虑计算一个二元母函数的高阶幂,$G(x, y) = F(x, y)^k$,可以得到 $F \frac{\partial G}{\partial_x} = kG \frac{\partial F}{\partial_x}$,由此可列得递推式子解出所有系数。计算高阶幂的复杂度是 $\Theta(k^2)$ 的。复杂度 $\Theta(k^2 \log n)$ 且常数较小。

事实上这也是可以直接用来做 $k=4$ 的情况的。

预计得分 $100\%$。

further

对于更大的 $d$,我们期望能得到一个怎样的做法?考虑 $d$ 次单位根的代数关系,我们希望能够基于一个它们的有理系数线性组合的基。由此则能将维度缩到基的维度,在其上进行快速幂。

我将说明维度可以达到 $\varphi(d)$,而数学迷告诉我说通过一些关于域的理论可以证明这也是下界,等我学了之后补一下证明

考虑分圆多项式 $\Phi_d(x) = \prod_{0\le k < d, \gcd(d, k) = 1} (x - \omega_d^k)$,它的次数显然是 $\varphi(d)$。

显然分圆多项式也可以用容斥原理重写,即

$$ \Phi_d(x) = \prod_{k|d} (x^{d/k}-1)^{\mu(k)} $$

显然该多项式的系数都是整数。由于 $\Phi_d(\omega_d) = 0$,不难得到 $\omega_d^k = \left.x^k \bmod \Phi_d\right \vert_{x=\omega}$。因此这个多项式的取模自然而然地导出了每个单位根到 $1, \omega_d, \dots, \omega_d^{\varphi(d) - 1}$ 的线性组合。

观察星象

算法一

显然所选的圆上要么有两个点要么有至少三个。因此分别枚举,枚举两个点作为圆的直径,此外枚举三个点之后可以确定唯一一个外接圆。

时间复杂度 $\Theta(n^4)$,预计得分 $10\%$。

算法二

$m=n$ 的情况就是经典的最小圆覆盖问题。

存在期望时间复杂度 $\Theta(n)$ 的算法,预计得分 $20\%$。

算法三

我们考虑二分答案。那么接下来枚举哪个点在圆上,其它每个点我们可以计算出它对于当前圆在哪个夹角区间内,进行一遍前缀和即可查找出是否存在一个夹角使得该圆包含 $m$ 个点。

时间复杂度 $\Theta(n^2\log n \log \frac{x}{\epsilon})$,预计得分 $60\%$。

算法四

我们对算法三进行进一步的观察。事实上我们可以看成这样一个问题,对于第 $i$ 个点,客观上存在一个 $r_i$ 即最小的圆的半径使得圆内有 $m$ 个点(我们认为无解即 $+\infty$),我们在二分的过程中,总共会预计调用 $\Theta(n\log \frac{x}{\epsilon})$ 次检查。这实际上是有所浪费的,在同样的检查次数内,实际上我们还可以算出每个 $i$ 对应的 $r_i$,换句话说我们多获取了不必要的信息,因为我们只关心最小值。

接下来就是有趣的地方了:我们先将序列随机打乱,并把当前最小值设为 $a = +\infty$。接下来我们将点一个个考虑,我们实际上可以一次检查出当前点的答案是否可以 $< a$。如果并不小于,我们就可以略过它,否则对该点的答案进行二分。这样显然是进行 $n + L\log \frac{x}{\epsilon}$ 次检查,其中 $L$ 是 $r_i$ 序列的单调栈长度。

显然最坏情况是 $r$ 互不相同时,单调栈长度的期望最长,这等价于一个排列的期望长度。而这一期望长度等于 $H_n = \sum_{k=1}^n \frac 1k = \Theta(\log n)$。

因此本算法的期望复杂度为 $\Theta( (n + \log n \log \frac{x}{\epsilon}) n\log n)$,预计得分 $100\%$。

BJ United Round #3

2019-09-04 10:30:06 By EntropyIncreaser
我们总是经不住考验所以需要被励志

也常常不明白命运带给我们磨难要表达的意思

向往安逸的人们总怀念着过去渴望能重返昔日

时间是一条永不回头的河流向着未来疾驰

                     ——幼稚园杀手《最初的我》

基本信息

出题人

  • T1:改编自一道 ProjectEuler
  • T2:某道题经 EntropyIncreaser 加强版本
  • T3:EntropyIncreaser

比赛难度

略低于 BJOI 2018 Day1。

BJ United Round #1 题解

2019-07-31 22:31:20 By EntropyIncreaser

极地科考

总负责人:EntropyIncreaser

简要题意:一个数列每次修改一个数,询问长度不低于 $k$ 的一段的最大平均值。

这是一道思维难度和代码难度都较低的题目,也与一些经典模型密切相关,故设计在第一题的位置。

算法一

枚举所有可行的区间,通过 $\frac ab - \frac xy = \frac{ay-bx}{by}$ 的正负来进行有理数比较。

时间复杂度:$\Theta(mn^2)$。

预计得分:$35$。

算法二

首先二分答案,假设答案为 $x$,那么将数列中每个数变为 $b_i = a_i - x$,我们只需知道是否存在一个长度至少为 $k$ 的段,其总和 $\ge 0$ 即可,维护一个前缀和的前缀最小值即可做到。最后,在二分的范围足够小时,则可以在 check 时记录找到的一个可行区间,来计算出其有理值答案。

时间复杂度:$\Theta(mn\log a)$。

预计得分:$60$。

算法三

注意到 $k=1$ 时直接选取序列中的最大值即为答案。

序列中的最大值有许多方式可以快速维护,例如线段树,堆。

时间复杂度:$\Theta(n + m\log n)$。

预计得分:$50$,综合前述得分 $75$。

算法四

考虑扩展算法三的观察,我们发现一定存在一个最优解的段长不超过 $2k - 1$,因为假设段长至少是 $2k$,那么我们将前 $k$ 个和后面的分别考虑,必有一段的平均值大于等于整段。

我们考虑分别对于长度 $L = k, k+1, \dots, 2k-1$,维护长度为 $L$ 的段中和最大的。当序列中一个值被修改时,只有最多 $L$ 个段包含它,因此只需修改堆中的 $L$ 个元素。

时间复杂度:$\Theta(nk + mk^2\log n)$。

预计得分:$85$。

算法五

考虑将修改一个元素看成将它加上一个值 $\Delta$,那么对于所有长度为 $L$ 的段,实际上是包含该位置的段内和全体加上这一值 $\Delta$,这些段对应于起点的一个区间。

我们每个 $L$ 用一个线段树来维护即可。

时间复杂度:$\Theta(nk + mk\log n)$。

预计得分:$100$。

梦中的项链

总负责人:EntropyIncreaser

这是一道中规中矩的计数题。这道题主要从容斥原理的角度考察了选手的组合计数能力,总的来说进行容斥的次数越多得分越多,最后考察了通过多项式技术进行优化计算。如果对于生成函数有一定了解则能在推导过程中事半功倍。

算法一

大力爆搜项链的重量序列?

时间复杂度:$\Theta(?)$。

预计得分:$0\sim 10$。

算法二

枚举第一个位置的重量 $w$,然后进行 DP。

设 $f(i, j)$:当前项链的总重量为 $i$,最后一个宝石的“抽象类型”为 $j$ 的方案数。抽象类型对于不为 $w$ 的每种宝石只有一种,对于 $w$ 重量的宝石来说有两种:是第一个位置的宝石种类或者不是。总共有 $\Theta(n)$ 种状态互相转移,因此一次 DP 的复杂度为 $\Theta(n^3)$。

时间复杂度:$\Theta(n^4)$。

预计得分:$20$。

算法三

注意到算法二中的状态之间互相转移可以通过记录全体的和进行优化,若重量转移 $j \neq k$,则方案数为 $a_k$,否则为 $a_k - 1$。这意味着我们实际上是先求和 $\sum_j f(i, j) \cdot a_k$,最后减去一个 $f(i, k)$。这样就可以减少一层枚举。

时间复杂度:$\Theta(n^3)$。

预计得分:$50$。

算法四

对于只有重量均为 $1$ 的情况,我们可以认为是给一个环进行上色,这是色多项式中的一个经典的模型,方案数应为 $(a_1 - 1)^n + (-1)^n(a_1 - 1)$。

时间复杂度:$\Theta(n)$。

预计得分:$10$,综合前述得分 $60$。

算法五

我们先不考虑第一个宝石和最后一个宝石的约束,那么剩下的约束就是一条简单的链,我们考虑先计算出此时的答案。

记宝石的生成函数为 $A(x) = \sum_{k \ge 1} a_k x^k$,我们考虑如何算出项链的生成函数。在此情况下,考虑 $F_n(x)$ 是有 $n$ 个宝石的链。我们假设最后一个宝石先随便放,再减去后面两个宝石相同的情况,再加上后面三个宝石相同的情况……可以得到式子 $$ F_n(x) = \sum_{k=1}^n F_{n-k}(x) A(x^k)(-1)^{k-1} $$ 我们考虑全体链的生成函数,也就是 $F(x)=\sum_{n\ge 0}F_n(x)$,将上式带入我们可以得到 $$ \begin{align} F(x)-1 &=F(x)\sum_{k\ge 1} (-1)^{k-1}A(x^k)\\ F(x) &= \frac{1}{1 - \sum_{k\ge 1} (-1)^{k-1} A(x^k)} \end{align} $$ 我们记 $I(x)=\sum_{k\ge 1} (-1)^{k-1}A(x^k)$。接下来考虑项链的约束。我们容斥的时候考虑最后一个宝石与序列的前 $p(p\ge 1)$ 个相同,与后 $q(q\ge 1)$ 个相同(包含自身),那么其贡献是 $A(x^{p+q})(-1)^{p+q-1}$。那么设 $p+q=j$,一共有 $j-1$ 组不同的 $p, q$,总共贡献是 $(j-1)A(x^j)(-1)^{j-1}$,我们记 $W(x)=1 + \sum_{j\ge 2}(-1)^{j-1}(j-1)A(x^j)$,看起来答案几乎就是 $F(x)W(x)$。

但是我们注意此时如果项链整体颜色相同的时候,贡献并不是 $0$,这是因为这个容斥假设不同的 $p, q$ 对应的方案是不同的,但是对于一个项链上的 $k$ 个宝石颜色全部相同,我们实际上在这个乘积中总共下来考虑了 $(-1)^{k-1}$ 次。因为我们的枚举方式实际上在某个位置插了一块板,而在所有宝石颜色都相同时候,这块板的位置不同就被计入多个方案了。因此我们要把这部分再减回去,也就是再减去 $I(x)$。

最后发现 $0$ 个宝石的方案恰被算了 $1$ 次,也要减去。故答案的母函数就是 $$ W(x)F(x)-I(x)-1 $$ 而运算过程中通共进行一次多项式求逆,外加一次多项式乘法。形如计算 $I(x)$ 的时候复杂度本身就是 $\Theta(n\log n)$。(事实上计算 $I(x)$ 可以做到 $\Theta(n\log \log n)$,但并非复杂度瓶颈,不予赘述)

算法复杂度:$\Theta(n\log n)$。

预计得分:$100$。

(之前的推导看起来混乱且理解起来十分费力。这里提供另一种思路。我们考虑对限制关系进行容斥,对于一个 $n$ 个宝石的项链,限制关系共有 $n$ 个,如果我们计入其中 $k$ 个限制,那么容斥系数就是 $(-1)^k$,从这个角度来看,就不需要在容斥的时候验证每个方案所被计算的贡献。)

求和

这道题来自 ROI2018 无进位加法。好像 cf 和 loj 上都没什么人刷,所以希望大家都没做过(

这是一道比较难的组合优化问题。其细节较多,得到平方级别的暴力还是比较轻松的,但是迈向正解还需要若干观察和分析。

算法一

考虑二分答案,即将答案从高到低决策该位是否可以为 $\texttt{0}$。这里二分的具体细节会导致复杂度不同,这里给出其中一个朴素二分中接近最高效的算法。

首先对答案进行估计:位数应当不超过 $n + \max L$。

其次我们考虑如果给出一个数,如果快速判断答案能否不大于它:我们从高到低分配给出的数中的每一个 $\texttt{1}$:我们将每个数当前最高位的 $\texttt{1}$ 放在一个数组里。

  • 如果当前所有数的最高位已经超过未分配数的最高位,则无解。
  • 否则如果相等,则我们只能将当前位分配给该数(如果有超过一个显然还是无解),可以看做该数直接将这一位去掉,如果有次高位,将次高位加入数组中。
  • 否则我们只要将这位分配给一个数,则该数直接清零,(考虑在一个可行解中交换分配的位)不难证明如果有解,我们可以用当前位将剩余的最大的数清零。
  • 考虑如何维护最大的数,我们预先将每个 $\texttt{1}$ 和它同位置有 $\texttt{1}$ 的数在一起排序,这个可以通过按顺序的基数排序来解决。然后我们每次取最大的数。
  • 看起来上述方法需要排序,实则不然,我们只需考虑在这位的数中是否会有一个没有被清零,因此我们并不需要执行排序,而只需要把最小的数放在最后处理。

由于每次考虑一位最多使得数组里插入一个元素,所以数组里的总元素是 $\Theta(n + \max L)$ 而非 $\Theta(\sum L)$。

时间复杂度:$\Theta(\sum L + (n + \max L)^2)$。

预计得分:$56$。

算法二

考虑所有数都只有一个 $\texttt{1}$ 的情况。

假设这些数从大到小排的最高位分别是 $L_1, L_2,\dots L_n$,那么答案的最高位可以发现是 $B = \max \{L_i + i - 1\}$。

这显然是一个下界,因为第一个达到这个数的 $i$,只考虑 $1\sim i$ 的这些数就必须至少占领这位。

而这显然有解,因为考虑将最大的数调至该位置,剩下的数对应的 $B$ 值至少 $-1$,归纳即得。

时间复杂度:$\Theta(n\log n + \sum L)$。

预计得分:$12$,综合前述得分 $68$。

算法三

上述观察事实上对于正解有着极大的意义。我们考虑将所有数都只保留最高位,得到的数列记为 $a'_i$,那么原数列的最高位应当在 $B$ 和 $B+1$ 之间。只需注意一点:将数列 $a'_i$ 每个数都左移 $1$ 位,那么得到的数均大于对应位置的 $a_i$,而这个数列的答案的最高位是 $B+1$!

我们考虑优化二分的过程。此时我们的决策要做的就更加明确了:确定 $B+1$ 这一位是否可以是 $\texttt{0}$。

考虑还有一些不用二分的位置:假设 $B$ 取到的第一个位置是 $k$,也就是说对于 $i < k$,都有 $L_i + i - 1 < B$,那么无论我们是从 $B+1$ 开始赋值,还是从 $B$ 开始赋值,这些数都一定不用在后面的计算中被考虑了,从整体上看,从 $B$ 位到 $B - k + 2$ 位的值都已经必然是 $\texttt{1}$ 了,而二分的结果其实额外决定的是对于前 $k$ 个数,还有一个 $\texttt{1}$ 是填在 $B+1$ 位置还是 $B-k+1$ 位置。

我们考虑先假设 $B$ 位足够,那么第 $k$ 个数就需要减去最高位后考虑剩下位的贡献,它会被重新插入进序列进行排序,我们可以通过算法一预先进行的基数排序,算出每个 $\texttt{1}$ 位置作为数的后缀,整体之间的序,然后用一个线段树来快速维护。

我们假设当前的二分如果返回可行,则已经计算出剩余部分的最优解。这样一来,二分的额外复杂度就是所有的返回“失败”的情况的复杂度之和。而在我们进行二分的时候,剩余的位数已经卡到了某个 $L_k$,如果二分失败,那么这一部分的复杂度就是 $\Theta(L \log \left( \sum L\right))$,注意到引发一次失败后,这个数就在接下来的过程中直接消失,因此不会被再次贡献复杂度。因此

时间复杂度:$\Theta\left(\left(\sum L\right) \log \left(\sum L\right)\right)$。

预计得分:$100$。

BJ United Round #1

2019-07-30 12:58:50 By EntropyIncreaser
2019,我们的命运越发紧密地联系在一起。

北京联合互测 (BJ United Round) 旨在促进北京各校间的交流和感情,提升北京整体的水平。

基本信息

出题人

  • EntropyIncreaser
  • EntropyIncreaser
  • 原题,数据和给分已经过相应调整。

比赛难度

本次比赛难度略简单于 NOI2019 Day1。

EI Round #1 题解

2018-06-08 18:01:17 By EntropyIncreaser

本次部分选手的成绩存在争议,请自重。我知道共同学习的同学确实会被培养出相似的思维模式,但是这次的提交时间之密集程度我认为超过了“同一种思维模式”可能达到的程度。

T1 狗

考察选手对集合的理解以及基本的聚合分析能力。

其实这道题和讲课的那道题是完全不同的,那道是选两个求交……

算法 1

我会 $\Theta(nq^2)$ 暴力!

预计得分 12 pts.

算法 2

考虑如何快速查询某个元素是否在这个区间里都有。

维护并查集,插入一个元素时如果左右有这一元素,就将其左右合并,维护每个并查集的最左和最右。

时间复杂度 $\Theta(nq + |S|)$ (对于此特殊应用,不带 $\alpha$ 常数)

预计得分 30 pts.

算法 3

考虑在插入单个元素时候对答案的影响,如果把元素插入到 $x$ 处,联系算法 2 我们可以想到,这使得 $l \in [l_{dsu}, x], r \in [x, r_{dsu}]$ 处的答案都 + 1 ,用一颗动态开点的树状数组套线段树即可维护。

当然,这里不能用真正的并查集,因为空间开不下,可以用 $\texttt{set}$ 等维护。

时间复杂度 $\Theta((|S| + q)\log^2 n)$

预计得分 50 pts.

算法 4

基于算法 3 ,对于一段空区间插入元素,可以变通一下,对于所有 $l, r \in [l_x, r_x]$ 的贡献都 +1 。而我们仍然暴力合并,每个原有区间实际上只会被用来合并两次:左一次、右一次。故一次区间修改引发的合并次数是均摊 $\Theta(1)$ 的。

时间复杂度 $\Theta(q\log^2 n)$

空间复杂度 $\Theta(q \log^2 n)$

预计得分 78 pts. ~ 100 pts.

算法 5 (std)

上面这个算法常数略大,而且空间略危险,所以我们考虑先将操作存下来,然后用离线树状数组来实现这个东西,跑得效果简直就像一个 $\log$。

空间复杂度就降到 $\Theta(q\log n)$ 辣!

预计得分 100 pts.

后记:很不幸这道题和前段时间 Codeforces 的一道题中解题思路有所共同,所以做过那道题的人可能想这道题就秒出答案了……然而其实我是先出题后来才打那场比赛的……

为什么会这样呢……明明是我先来的,第一次有了开比赛的打算,又有了好的题目点子,两件快乐事情重合在一起。而这两份快乐,又给我带来更多的快乐。但是,为什么会变成这样呢……我的 OI 怎么就白学了呢……

T2 梨

考察选手对数据结构优化建图的能力。

其实吧,这道题重点只想考察一个优化方法……而且我出完这道题后才发现和 POI 撞了,就当给大家送温暖了……

算法 1

对每个供应商连出 $k_i$ 条边,这些边的费用是 0。对每个原点到供应商的连边,有流的上限 $t_i$ 和费用,而对每天拆点,有下界。跑最小费用可行流

时间复杂度 $\mathrm{MinimumCostFlow}(|V| = \Theta(n + m), |E| = \Theta \left(\sum k_i \right))$

预计得分 45 pts.

算法 2 (std)

考虑优化一些连边。

考虑到供应商仅仅是对一个区间的点连边,我们考虑额外建立一些虚点。

一个方法是造一颗线段树,然后每个供应商只需要连出 $\Theta(\log n)$ 条边啦!

时间复杂度 $\mathrm{MinimumCostFlow}(|V| = \Theta(n + m), |E| = \Theta(n + m\log n))$

当然,你也可以通过 ST 表的形式来维护,那就是 $\mathrm{MinimumCostFlow}(|V| = \Theta(n\log n + m), |E| = \Theta(n\log n + m))$

预计得分 100 pts.

T3 锅

考察选手对傅里叶变换的基本认识,对分治思想的基本理解,对“将执行过程作数据化处理”的推广能力,对分块法的基本掌握。

算法 1

对于每次修改,只考虑本修改对 FFT 后的数组的影响

算法 2

对每次查询,暴力执行过程。

前两种算法并用 $\Theta((q_{\min} + \log n)n)$,预计得分 22 pts.

56 pts. 这个档嘛……就是一个分档,如果暴力写的太强或者正解写的常数太大就是这个分……

算法 3 (因为常数优秀所以成了 std)

考虑对时间进行分块。

设时间块大小 $b$,我们每过 $b$ 次修改就将新的系数数组重新做一遍 FFT ,每次查询对于未纳入 FFT 的修改暴力计算对答案的影响。

发现时间是 $\Theta \left(n\log n \cdot \frac q b + qb\right)$ ,取 $b = \sqrt{n\log n}$ 有复杂度 $\Theta \left(q\sqrt{n \log n} + n\log n\right)$

预计得分 100 pts. ,因为它的常数足够优秀,但是为了追求理性的愉悦,本题原本应该的 std 是接下来这种方法:

算法 4 (废弃的 std)

类比线段树等数据结构,考虑对 FFT 算法的执行流程用数据结构记录下来,

回顾按奇偶性分治,我们可以将二进制数位的逆序建出一颗 01-Trie,但是查值仍然要递归到两个子节点,复杂度似乎没有改善。

考虑我们有上面的两种暴力:

暴力 1 是修改线性,查询立刻的。

暴力 2 是修改立刻,查询线性的。

当子树大小达到了阈值 $2^b$,我们对它使用暴力 1 ,对于不到的时候我们进行分治,此时类似暴力 2 。

这样我们的修改在树上只是走到一个截止的大叶子上然后进行修改,时间 $\Theta\left(2^b\right)$。

我们的查询在树上则要覆盖一些区间,但到叶节点的查询就是立即的,时间 $\Theta \left(2^{k - b}\right)$。

当然,你并不必要真的把这棵树显式地构建出来,另一种理解方法是它在进行一个截半的蝴蝶操作,写起来应该是可以像数列分块一样的。

令 $b = \frac k 2$, $2^b = \sqrt n$ ,时间复杂度 $\Theta(q \sqrt n + n\log n)$ ,预计得分 100 pts.

这个做法可能是我写的常数太大,虽然也能通过,但是跑得反而比按时分块要慢,有哪位哥哥写一个优雅还跑得快的版本呀?

算法 ???

如果你有复杂度优于出题者的算法,欢迎在讨论区发表,出题人十分期望见到 $O(\operatorname{poly} \log n)$ 每次修改的做法。

EI Round #1

2018-06-05 11:07:04 By EntropyIncreaser

大家好,大家可能不大认识我这个蒟蒻,我来自一所叫做大泥湾艺术学校的中学。总之今天不知道为什么我就来开了个比赛

基本信息

EI Round #1 将于 北京时间 6 月 8 日 13:00 ~ 18:00 (June/8/2018 13:00 UTC+8) 举行,欢迎校内外同学参加。

IOI 赛制,采用 subtask 评测

比赛一共包含 3 道题目,理论上记 rating 。

代码难度:提高+/省选-

思维难度:省选偏奇幻(由于一些原因,可能实际效果会简单一些)

出题阵容

题目提供者:三道题均由 EntropyIncreaser 提供

验题人:mzhmxzh (OIer 里最会敲鼓打击乐的)、milkfillingsamsara_1

温馨提示

毫不卡常(所有时间限制均在 无底层优化的 std 的时限二倍以上!)

upd: 好吧,刚刚测了一下,std 稍微超过了时限一半……但是相信大家的正确算法肯定是可以通过的!

不要在意奇怪的题目名称

浅谈高阶前缀和的动态维护

2018-05-29 19:36:49 By EntropyIncreaser

某天无聊,就把某题的结论扩展了一小下,其实也挺 naive 的,姑且发到这里娱乐一下吧……

摘要

对于单点修改,区间查询的问题在树状数组上可以实现。而对于区间修改通过线段树可以实现,区间查询本质上是维护一个 2 阶前缀和的问题,这个问题后来也被提出在树状数组上的解决方法。

而到拓展到高维的情况,或者对于更高阶的前缀和,仍然可以通过树状数组解决。下文我们对于一个 $\ell$ 维 $k$ 阶前缀和问题,可以在 $\Theta\left(nk\right)$ 预处理,单点操作 $\Theta\left( \left (k\log n \right)^\ell \right)$ 的效率内解决。

特别对于位置离散的情况,在一维情况下我们可以做到单点修改 $\Theta \left(k \left(\log k + \log n \right)\right)$,单点查询 $\Theta(k\log n)$ 的效率完成。

一些约定记号

$_\ell a_\mathbf{x}$ 表示一个 $\ell$ 维数组,在维度不重要的情况下会省略左下角的维度。其中 $\mathbf{x}$ 是坐标向量。

对向量的比较 $\mathbf y \le \mathbf x$ 为各分量的偏序关系比较,即

$$ \mathbf y \le \mathbf x \Leftrightarrow \forall 1 \le i \le \ell(\mathbf y_i \le \mathbf x_i) $$

定义两个数列的卷积 $(a * b)_n = \sum_{i = 1}^n a_i b_{n - i}$

对于一维前缀和,我们定义对于数列 $\{a_n\}$ 的前缀和 $\sigma(a)_n = \sum_{i = 1}^n a_i$ ,可以通过拆分求和符号得到性质 $\sigma(a)_n = \sigma(a)_{n - 1} + a_n$ 。

我们递归地定义 $k$ 阶前缀和 $\left\{\sigma^k(a)_n \right\}$ , 其中 $\sigma^0(a)_n = a_n$ :

$$ \sigma^k(a)_n = \sum_{i = 1}^n \sigma^{k - 1}(a)_i = \sigma^k(a)_{n - 1} + \sigma^{k - 1}(a)_n $$

对于高维数组的前缀和,我们定义为:

$$ \sigma^k(a)_{\mathbf x} = \sum_{\mathbf y \le \mathbf x} \sigma^{k - 1}(a)_{\mathbf y} $$

感觉创造了一堆冗余的东西呢……

卷积器

求和数列的重要性

考虑到卷积对点加构成分配律,那么记前缀和求和器数列为 $\sigma^k$ ,我们有

\begin{eqnarray*} \sigma^k(a + d) & = & (a + d) * \sigma^k \\ & = & \sigma^k(a) + d * \sigma^k \end{eqnarray*}

对于单点修改时,数列 $d$ 只在某个地方有值,所以等价于将 $\sigma^k$ 平移并乘上一常数。

与组合数的关联

对于求和数列,我们有它规约到组合数的通项公式:

$$\sigma^k_n = {n + k - 2 \choose n - 1}$$

由之前的定义,我们整理出求和数列的以下一些性质:

$$\sigma^k_1 = 1$$

$$\sigma^k_n = \sigma^{k - 1}_n + \sigma^k_{n - 1}$$

注意到这与组合数的性质非常相似:

$${n \choose 0} = 1$$

$${n \choose m} = {n - 1 \choose m - 1} + {n - 1 \choose m}$$

可以数学归纳法证明这一结论。

注意到当 $k$ 为常数的时候,这是一个关于 $n$ 的 $k - 1$ 次多项式。

$$ \sigma^k_n = \frac {n (n + 1) \cdots (n + k - 2)}{(k - 1)!} $$

高维推广

给出高维求和数列的通项公式:

\begin{eqnarray*} _\ell\sigma^k_{\mathbf n} & = & \prod_{i = 1}^\ell \sigma^k_{\mathbf n_i} \\ & = & \prod_{i = 1}^\ell {\mathbf n_i + k - 2 \choose k - 1} \end{eqnarray*}

注意到高维求和数列的取值是一个 $\ell$ 个元素的可重偏序集的可能取值数量,可以看出在计数时维度之间是独立的。故立得答案数量是各维度的乘积。

算法

树状数组维护多项式

根据之前的两条引理,我们可以得到:一个单点修改的 $k$ 阶前缀和的影响可以看做单点修改的一个组合数,而且恰好是 $k - 1$ 阶多项式,对于一个高维的情况,影响是在各维度上的乘积。

我们只需要 $\Theta(k^2)$ 的时间就可以计算出原多项式 $\sigma^k(x)$ ,发现系数的变换符合卷积形式,可以优化到 $\Theta(k\log k)$,不过在 $k$ 较小的时候并不重要。而在位置比较稠密的时候可以递推出每平移一格的多项式系数表达,因为 $\sigma^k(x)$ 是一个上升幂的形式,相邻平移只需要和一次多项式做乘除法。

在树状数组维护时,无论是修改还是查询,都只会进行 $\Theta(\log n)$ 次多项式加法,而询问完的多项式取值可以 $\Theta(k)$ 回答,所以这部分的时间是 $\Theta(k\log n)$ 。而对于高维的情况, 我们需要维护每一种 $x^ay^b\dots$ 的系数,总共有 $k^\ell$ 项,树状数组的时间也有变化。我们需要 $\Theta \left( (k\log n)^\ell\right)$ 的时间来完成。

如果在一维上的离散情况,我们则可以用一颗平衡树来维护,而其他部分的大致思想没有变。但考虑到位置是离散的,则不能对多项式进行预处理。

打脸

我这玩意不都被下降幂相关给研究透了吗?

例题

这玩意有什么用吗?答:当然没有啦!

1,「BJOI2018」链上二次求和

好吧……这个问题其实是我在 BJOI 之后扩展想的问题,最开始是想解决链上二次求和。

我个人的思路比较清奇,因为不喜分类讨论,故不想写那个分段二次函数插到线段树上面的方法,然后经过一波分析,发现可以用树状数组维护一个 4 阶前缀和。考场上因为之前耗在了一个被写错的 DP (而且到最后都是错的),到这道题就草草写了一个线性扫描的暴力水了 50 分。

具体的分析过程在我的这篇文章里面。

跑得常数也蛮小的,而且我消耗的内存似乎是最少的嘞

2,「APIO2016」Boat

我 yy 的算法确实是 $\Theta(n^3)$ 的,不过常数实在太大,开了各种优化也没卡过 uoj 的时限……

发现 DP 数组是一个被切成 $n + 1$ 段的函数,每段内部的函数都是很有规律的,可以归纳证明成上文“前缀和函数”的和,阶是 $n$ 级别的,即

$$f(x) = \sum_{k = 0}^n a_k \sigma^k(x)$$

那么这个时候我们就考虑别把它转成多项式,直接用这个东西的系数来存!

这样每次 DP 其实在累加前缀和的时候就是把系数都错位挪了一下,修改是 $\Theta(n)$ 的。

而对函数的一段求值也可以通过高阶前缀和间的乘除递推关系搞出 $\Theta(n)$ 的求法。

共 9 篇博客