「ZJOI 的部分题解整理」
ZJOI2017D1:http://jiruyi910387714.is-programmer.com/posts/209073.html 。
ZJOI2017D2:http://jiruyi910387714.is-programmer.com/posts/212169.html 。
ZJOI2018D1:http://jiruyi910387714.is-programmer.com/posts/212412.html 。
ZJOI2018D2:http://jiruyi910387714.is-programmer.com/posts/212639.html 。
会把之前的 ZJOI 题解(陆续更新) + 新的 ZJOI 题解整合在一起。
水平有限,所以仅有一部分题解。
从 ZJOI2016 开始,之前的题如果有时间再补上吧。
题目在 uoj/loj 上都可以找到。
「ZJOI2016」旅行者
网格图分治,每次选 \(n, m\) 中较大的进行分割。
对于跨越分割线的询问,枚举分割线上的点求最短路。
记 \(S = n\times m\),复杂度 \(O(S\sqrt{S}\log S)\)。
「ZJOI2016」小星星
记 \(f_{x,i,s}\) 表示把 \(x\) 映到 \(i\) 上,子树内所有映到 \(s\) 上的方案数,复杂度 \(O(n^2\times 3^n)\)。
发现是子集卷积,考虑对占位多项式 \(g_{x,i,t,s} = [popcount(s)=t]\times f_{x,i,s}\) 进行 fwt,中途不需要反演回来,复杂度 \(O(n^4\times 2^n)\)。
注意到对于某一个 \(x\),只有当 \(t = size(x)\) 时 \(g_{x,i,t,s}\) 才有值,所以可以省掉一维,复杂度 \(O(n^3\times 2^n)\)。
用容斥可以推得类似的做法。
「ZJOI2016」大森林
将树的序列看作时间轴,从左往后扫描,在 \(l\) 处加入操作,在 \(r + 1\) 处弹掉操作。
可以通过简单转化保证操作 1 对应的树全部含点 \(x\),那么 0 操作的区间不再重要。
考虑操作 1 对应的修改,即将一些点的父亲全部修改为某个点,可以通过建虚点 + lct 完成这一操作。
注意询问不能直接查路径上的实点数量。
「ZJOI2016」线段树
记 \(dp(i,l,r,x)\) 表示第 \(i\) 次操作以后,\(a'_{l\dots r}\leq x\) 且 \(a_{l-1}>x,a_{r+1}>x\) 的概率。即 \(\leq x\) 的极长区间。
注意到转移时 \(x\) 并没有影响,\(x\) 只影响初态 \(dp(0,l,r,x)\)。
且已知 \(dp\) 后求答案只需要 \(\sum_{x}dp(q,l,r,x)\) 和 \(dp(q,l,r,\max\{a_i\})\),因此可以去掉 \(x\) 这一维。
时间复杂度 \(O(n^3)\)。
「ZJOI2017」仙人掌
可以将问题转化成 “树上不交链划分”,这里的不交指没有公共边。
考虑求出每个点 \(x\) 的贡献,发现只与 \(x\) 的度数 \(deg(x)\) 有关,记作 \(f_{deg(x)}\)。
预处理 \(f\) 即可。
「ZJOI2017」树状数组
正常做只有当 \(x\leq y\) 时 \(x\to y\) 产生贡献,反着做即 \(x\geq y\) 时 \(x\to y\) 产生贡献,即后缀和(注意特判 \(0\))。
那么等价于查询 \(A_{l-1} = A_r\) 的概率,树套树即可(再次强调,注意特判 \(l=1\))。
「ZJOI2017」多项式
首先有个结论:\(f^2(x) = f(x^2) \bmod 2\)。证明直接展开观察。
考虑对 \(f(x)\) 维护所有 \(2^{\max(n, k)}\) 种长度为 \(\max(n, k)\) 的子串的出现次数,支持两种操作 \(f(x)\to f(x^2), f(x)\to g(x)f(x^2)\),其中 \(g(x)\) 是原字符串。
新的字符串中所有长度为 \(\max(n, k)\) 的子串可以对应回原字符串的某个长度为 \(\max(n, k)\) 的子串,为了处理的方便,我们唯一对应到最前面一个。
为什么说这样处理方便?\(f(x)\to f(x^2)\) 倒无所谓,对比 \(g(x)f(x^2)\) 与 \(g(x)\) 单独乘 \(f(x^2)\) 中某个长度为 \(\max(n, k)\) 的子串,两个乘出来的结果实际上只有最高的两位是相同,所以可以只维护长度为 \(\max(n, k)\) 的子串的出现次数来进行转移。
但是这种处理方式不能适用于最前和最后的,因此还需要单独维护最前最后若干个。
区间查询可以转成前缀查询,在转移时再加上一个截断操作即可。时间复杂度 \(O(2^{\max(n, k)}m)\)。
我写了个 \(O(2^n\log mk)\) 的不知道在干啥的代码,大概是只能在 loj 跑过去的假做法。
「ZJOI2017」线段树
询问可以拆成两条链 \(l\to lca\) 与 \(r\to lca\)。可以考虑使用倍增找到询问点 \(u\) 的位置,并维护 “被定位的点” 的信息。
「ZJOI2017」字符串(没写)
首先,可以通过分块维护 hash 实现 \(O(\sqrt{n})\) 修改,\(O(\log n)\) 查询 lcp。
对于字符串 \(t\),我们称它的后缀 \(t[i:]\) 是待选最小后缀,当且仅当存在以 \(t\) 为前缀的 \(t'\)(即在 \(t\) 后加若干字符得到 \(t'\))使得 \(t'[i:]\) 成为最小后缀。
对于 \(t\) 的某两个后缀,如果它们不存在前缀关系,则直接扔掉字典序大的;否则说明短的是长的 border。这样得到一系列互为 border 的待选后缀。
引理:如果 \(p\) 是 \(q\) 的 border,且 \(|q| / 2 < |p| < |q|\),则 \(p\) 不可能是待选最小后缀。
首先,此时必然存在一个 \(p'\) 是 \(p\) 的 border 且 \(|p'| \leq |q| / 2\)。
在这种情况下,要么 \(q\) 优于 \(p\) 优于 \(p'\),要么 \(p'\) 优于 \(p\) 优于 \(q\)。
这得到一个重要结论:我们所需要维护的后缀集合其实很小。
那么可以使用线段树进行维护,每次上传时合并两个大小为 \(O(\log n)\) 的后缀集合,合并一次复杂度 \(O(\log^2 n)\)(注意求 lcp 是 \(O(\log n)\) 的)。
因此,总复杂度 \(O(m\sqrt{n} + m\log^3n + n\log^2n)\)。
实现上好像没有什么特别的困难,不写了。
「ZJOI2018」历史
考虑每个点 \(x\) 的贡献,记 \(sum_x=\sum_{y\in subtree(x)}a_y\),只需要判断是否存在一棵子树的 \(sum_{ch} > \frac{sum_x}{2}\)(或者 \(a_x > \frac{sum_x}{2}\))。
注意到所有点的上界可以同时取到,因此这就是答案。那么只需要动态维护每个点满足 \(sum_{ch} > \frac{sum_x}{2}\) 的重儿子 \(ch\) 即可。
考虑像 lct 一样每条重链用一棵平衡树维护,此时内层 splay 森林的复杂度分析一样,轻重边切换的复杂度分析需要稍微改改,还是可以证得 \(O(n\log n)\) 的复杂度。
(这个思路还可以用于动态维护重链剖分,即今年北大集训的 D3T3)。
「ZJOI2018」迷宫
可以建出最基础的自动机:共 \(K\) 个点,代表模 \(K\) 下的完全剩余系。
(以下运算如非特殊说明,默认在模 \(K\) 意义下进行)
两个结点 \(x, y\) 可以合并的充要条件:
对于任意非负整数 \(i\) 与任意非负整数 \(r < m^i\);
满足 \(m^ix + r = 0\) 与 \(m^iy + r = 0\) 同时成立或者同时不成立。
据此,有一个 trivial 的暴力:
从小到大遍历所有 \(i\),将 \((-m^i, 0]\) 里面存在的结点全部提取出来,这些点不能再和其他点合并。
然后将剩下的点 \(x_1, x_2, \dots, x_t\to mx_1, mx_2, \dots, mx_t\),如果 \(mx_i = mx_j\) 则合并,进行下一轮的迭代。
如果没有点存在,则迭代结束。由于某一时刻会有 \(m^i \geq K\),因此迭代总会结束。
然后考虑加速这一过程。
记 \(d_i = \gcd(m^i, K)\),则在第 \(i\) 轮中存在的点 \(x\) 满足 \(d_i | x\)。
(1)如果 \(d_k = d_{k + 1}\)(或者说 \(d_{k + 1} / d_k = \gcd(m, K / d_k) = 1\)),则在第 \(k\) 轮及其之后不会再有点会被合并,因此可以直接在第 \(k\) 轮停下。
(2)如果所有 \(0d_k, 1d_k,\dots\) 在第 \(k\) 轮中都存在,那么 \(0d_{k + 1}, 1d_{k + 1}, \dots\) 在第 \(k + 1\) 轮中都存在的充要条件为 \(K / (d_{k + 1} / d_k) \leq K - m^k\)。
这是因为从 \(k\to k + 1\) 将会有 \(d_{k + 1} / d_k\) 个结点合并成一个,这些结点在模 \(K / (d_{k + 1} / d_k)\) 意义下相同,因此只需要保留前 \(K / (d_{k + 1} / d_k)\) 个即可生成下一轮中的所有数。
反过来当 \(K / (d_{k + 1} / d_k) > K - m^k\) 时,由于 \(d_k \neq d_{k + 1}\),此时有 \(m > 1, d_{k + 1} / d_k > 1\),那么可以推出 \(m^{k + 1} > K\),因此第 \(k\) 轮及其之后不会再有点会被合并(毕竟在 \(k + 1\) 轮时就停下来了)。
(3)如果以上两个条件判定后都没停下来,则此时必然有 \(0d_k, 1d_k,\dots\) 在第 \(k\) 轮中都存在,那么被提取出来的结点数量即为 \(m^k / d_k\)。
当然,条件 1, 2 可以整合在一起,因为 \(d_k = d_{k + 1} \Rightarrow K / (d_{k + 1} / d_k) > K - m^k\)。
官方题解说复杂度是 \(O(T\log K)\)。但求 gcd 也要 \(O(\log K)\) 求,所以我觉得应该是 \(O(T\log^2K)\)?
「ZJOI2018」胖
考虑二分每个点 \(x\) 所能更新的区间 \([l_x, r_x]\) 对应的 \(l_x, r_x\)。
需要找到 “能在 \(x\) 到达之前到达二分点 \(mid\) 的 \(y\),距离 \(mid\) 的最短距离”,可以预处理 st 表 + 二分查找。
时间复杂度 \(O(n\log^2n)\),此处认为 \(n, \sum K\) 同阶。注意点的贡献不要算重。
本题存在 \(O(n\log n)\) 的做法,详见 https://blog.csdn.net/zxin__/article/details/82925838。
「ZJOI2018」保镖(没写)
官方题解:https://blog.csdn.net/qq_16267919/article/details/80100413 。
(1)凸包点数转化成三角剖分的数量(利用欧拉定理)。为了方便,特化成 Delaunay 三角剖分。
定义经过至少三个点且严格内部不含任何点的圆为内圆。
内圆与 Voronoi 图的顶点一一对应;进一步地,由于 V 图和 D 剖是对偶的,因此内圆和三角剖分中每个三角形一一对应。
定义经过至少三个点且严格外部不含任何点的圆为外圆。
(2)反演中心在圆内,则内圆变外圆,外圆变内圆;否则内圆依然是内圆,外圆依然是外圆。
因此只要找出所有内圆和外圆,最后做圆与矩形的面积交即可。
圆的方程 \(x^2 + y^2 + Dx + Ey = F\)。
考虑记 \(z = x^2 + y^2\),则该方程可以改写成平面方程 \(z + Dx + Ey = F\)。
也即,将平面点 \((x, y)\) 映到三维抛物面 \((x, y, x^2 + y^2)\) 上,圆的分割变成了平面分割。
由于平面是线性的,所以方便很多。
(3)内圆和三维凸包中的下凸面一一对应,外圆和三维凸包中的上凸面一一对应。
做一个三维凸包即可,这道题 \(O(n^2)\) 都可。
因为实在不会写三维凸包,于是咕掉咕掉。
由于 V 图和 D 剖是对偶的,所以内圆个数、V 图顶点数都是 \(O(n)\) 的。这个结论可以应用于 「ICPC World Finals 2018」熊猫保护区。
另外,有一道同样是 “映到三维抛物面” 这一想法的题目 CF549E,本质上判三维凸包是否有交。
很好看的 gif(关于 V 图的):
其他的碎碎念:
关于 \((x, y)\mapsto (x, y, x^2 + y^2)\) 这一变换,我感觉很奇妙。
是否其他二次曲线都有类似的变换呢?或者说,一些其他曲线还可以映到更高维的线性对象上?
这个做法抽象了问题的本质吗?上面的 CF549E 事实上代码实现并没有依赖于三维凸包。那么,是否有更直接的推导呢?
「ZJOI2019」麻将
我没打过麻将,所以不会用专业术语。
考虑给定一个牌组,如何判断是否可胡。
自然的想法是设 \(f_{i, 0/1, j, k, t}\) 表示考虑前 \(i\) 种牌,取过/没取过对子,第 \(i - 1\) 种牌剩 \(j\) 张,第 \(i\) 种牌剩 \(k\) 张,已经有 \(t\) 个对子,的最大面子数。
如果 \(\forall 0\leq j, k\leq 4,0\leq t < 7\),都有 \(f_{n, 1, j, k, t} < 4\),则不可胡。
注意到三个相同的 \(i, i + 1, i + 2\) 形式的总可以换成三个 \(i, i, i\),则另一个较优的状态设计为:保留 \((i - 1, i)\) 共 \(j\) 组,保留 \(i\) 共 \(k\) 张。则此时有 \(0 \leq j, k\leq 2\)。
观察到 \(f_i\) 实际上保留了前 \(i\) 种牌的所有信息,这样算出本质不同的不可胡的牌组数的一个上界是 \(5^{2\times 3\times 3\times 7}\)。
不过这个上界非常松,实际上搜出来本质不同的不可胡的牌组数只有 2091 种。
搜出所有牌组与它们之间的转移(实际上形成了一个 DFA),再回到原问题就比较套路了:设 \(dp_{i, j, s}\) 表示前 \(i\) 种牌共选了 \(j\) 张牌,得到的牌组状态为 \(s\) 的方案数。
存 \(j\) 是为了方便算期望。由于牌互不相同所以转移时还要乘组合数。
「ZJOI2019」线段树
考虑 dp:定义 \(dp(0/1, 0/1, x)\) 表示 x 的祖先结点是否有 tag,x 本身是否有 tag,这 4 种情况分别对应的方案数。
观察 dp 的转移,发现只有线段树上单点修改/子树修改,单点询问/子树询问。
直接维护一下线段树上的 单点 dp 值/转移矩阵 与 子树 dp 值/转移矩阵 即可。
时间复杂度 \(O(n\log n)\)。
「ZJOI2019」Minimax 搜索
记根对应权值为 \(rt\),尝试求出 \(\max_{i\in S}|i-w_i|\leq k\) 的集合数。首先特判集合中包含 \(rt\) 的情况。
给定 \(k\),可以求出每个 \(> rt\) 的叶子是否可以 \(< rt\),每个 \(<rt\) 的叶子是否可以 \(> rt\)。
记 \(f(x)\) 表示只把 \(> rt\) 的改小使 \(w_x\) 变小的集合数,记 \(g(x)\) 表示只把 \(< rt\) 的改大使 \(w_x\) 变大的集合数,可以 dp。
发现每一个 \(k\) 都要重新 dp,可以按 \(k\) 从小到大的顺序进行动态 dp。
「ZJOI2019」开关
记 \(q_i=\frac{p_i}{\sum p_i}\)。
一种推导方法是使用集合幂级数:
记 \(f_S\) 表示当前开关状态为 \(S\),到达终态的期望步数。有转移:
\[\begin{cases} f_S &= 1+\sum_{i=1}^{n}f_{S\ \rm{xor}\ 2^i}\times q_i\\ f_0 &= 0 \end{cases} \]记 \(I\) 表示全 \(1\) 的序列,给不满足转移的 \(f_0\) 加上偏移量 \(k\),可得卷积式:
\[F\otimes Q + I = F+k \]即 \(fwt(F)\times fwt(Q-1)=fwt(k-I)\)。
由于 \(fwt(F)_S = \sum_{T}(-1)^{|S\cap T|}f_T\),得:
\[\begin{aligned} fwt(Q-1)_S&=\sum_{i\not\in S}q_i-\sum_{i\in S}q_i-1\\ fwt(k-I)_S&=-\sum_{T\subseteq S}(-1)^{|T|}\times 2^{n-|S|}+k \\ &=-2^{n-|S|}\times\sum_{i=0}^{|S|}{|S|\choose i}(-1)^i+k \\ &=k-[S = \empty]\times 2^n\\ \end{aligned} \]因此 \(fwt(Q-1)_{\empty} = 0\),推得 \(fwt(k-I)_{\empty} = k-2^n=0\),解得 \(k = 2^n\)。
当 \(S\neq \empty\) 时,有 \(fwt(F)_S = \frac{fwt(k-I)}{fwt(Q-1)} = \frac{2^n}{\sum_{i\not\in S}q_i-\sum_{i\in S}q_i-1}\)
由于 \(ifwt(F)_S = \frac{1}{2^n}\sum_{T}(-1)^{|S\cap T|}f_T\)。则:
\[\begin{aligned} F_S &= \frac{1}{2^n}(\sum_{T\not =\empty}\frac{(-1)^{|S\cap T|}\times 2^n}{\sum_{i\not\in T}q_i-\sum_{i\in T}q_i-1} + fwt(F)_0) \\ &= \sum_{T\not =\empty}\frac{(-1)^{|S\cap T|}\times\sum p_i}{\sum_{i\not\in T}p_i-\sum_{i\in T}p_i-\sum p_i} + \frac{fwt(F)_0}{2^n} \\ &= -\sum_{T\not =\empty}\frac{(-1)^{|S\cap T|}\times\sum p_i}{2\sum_{i\in T}p_i} + \frac{fwt(F)_0}{2^n} \end{aligned} \]如果已知 \(\frac{fwt(F)_0}{2^n}\),以 \(\sum_{i\in T}p_i\) 为状态作背包 dp 就可以 \(O(n\sum p)\) 算出 \(F\) 的某一项了。
注意到还有条件 \(F_0 = 0\),可以通过这个条件解出 \(\frac{fwt(F)_0}{2^n}\)。
也可以用 EGF 推:
记 \(F(z) = \prod_{i=1}^{n}(\frac{e^{q_iz}+e^{-q_iz}}{2})\) 表示回到原状态的 EGF。
记 \(G(z) = \prod_{i=1}^{n}(\frac{e^{q_iz}+(-1)^{s_i}e^{-q_iz}}{2})\) 表示永不停止,按到终态的 EGF。
记 \(\hat F(z),\hat G(z)\) 分别是 EGF 对应的 OGF,则 \(\frac{\hat G(z)}{\hat F(z)}\) 表示终态停止的 OGF,答案即为 \((\frac{\hat G(z)}{\hat F(z)})'|_{z=1}\)。
设 \(F(z)=\sum a_k e^{kz}\),则 \(\hat F(z) = \sum \frac{a_k}{1-kz}\)(此时 \(k\) 形如 \(\frac{\sum_{i\in S}p_i}{\sum p_i}\))。同理 \(\hat G(z)=\sum\frac{b_k}{1-kz}\)。可以背包求出所有 \(a, b\)。
由于 \((\frac{\hat G(z)}{\hat F(z)})'|_{z=1} = (\frac{\hat G'(z)\hat F(z)-\hat G(z)\hat F'(z)}{\hat F^2(z)})|_{z=1}\),但是 \(\frac{1}{1-z}\) 不能直接带,所以上下同乘 \(1 - z\)。
「ZJOI2019」语言
维护端点在 \(x\) 子树内的链构成的并集,则贡献为链并集中 dfs 序在 \(x\) 之前的点数。
链并集可以通过 \(\sum dep_{a_i} - \sum dep_{lca(a_{i-1},a_i)}\) 计算,其中 \(a_i\) 为按 dfs 序排好的链端点。注意 \(a_i\) 是连成环的,也即还需要计算首尾的 lca。
链并集中 dfs 序在 \(x\) 之前的点数,可以把 \(x\) 加入其中并弹掉 \(x\) 之后的点。
于是线段树合并即可,时间复杂度 \(O(n\log^2 n)\)(求单次 \(lca\) 视作 \(O(\log)\))。
「ZJOI2020」传统艺能(不做)
好像是道水题啊,不想思考,不做了。
「ZJOI2020」序列
记 \(\mathcal S\) 表示所有操作的集合,每种操作 \(T\in\mathcal S\) 由该操作影响到的所有位置组成。
容易列出线性规划:
将其化成标准型并对偶:
首先,我们假设它就是整数规划。
这些不等式对应的实际含义:原序列任意区间的和 \(\leq 1\);奇/偶数位置构成的子序列,任意区间的和 \(\leq 1\)。
注意到 \(\phi_i < -1\) 是一定不优的(将其换作 \(\phi_i = -1\) 一定也合法),所以 \(\phi_i\) 的取值只有 \(0, \pm 1\)。
那么这些不等式等价于:原序列任意两个 \(1\) 之间至少隔了一个 \(-1\);奇/偶数位置构成的子序列,任意两个 \(1\) 之间至少隔了一个 \(-1\)。
然后做一个 \(O(n)\) 的 dp 即可。
「ZJOI2020」抽卡
考虑一个已经抽取 \(i\) 张牌,且仍未合法的局面。抽出该局面的概率为 \(1/\binom{n}{i}\),下一次抽中不重复的期望步数为 \(n/(n-i)\)。
因此,设 \(f_i\) 表示 \(i\) 张牌且仍未合法的局面数量,则答案为 \(\sum n/(n-i)\times1/\binom{n}{i}\times f_i\)。
考虑求出每个连续段对应的生成函数 \(A_i(x)\),则 \(\prod_i A_i(x)\) 即是我们想要的。
以下设连续段长度为 \(s\),我们在最后加一个空白变成 \(s + 1\)。
从组合对象的角度,由于不存在 \(k\) 个连续被抽中的,总可以将其划分成 “\(i < k\) 个抽中的 + \(1\) 个没抽中的”。
划分出来的每一段对应生成函数 \(F(x) = x\frac{1-x^k}{1-x}\),总方案数 \([x^{s + 1}]1/(1-F(x))\)。
然而我们并不是求总方案数。考虑求出 \([x^{s + 1}]F^i(x)\),它对应了 “没抽中 \(i\) 个” 的方案数,反过来就抽中了 \((s + 1 - i)\) 个。
这是个经典拉反,可以在预先牛迭求出 \(F\) 的复合逆,再 \(O(s\log s)\) 的时间内求出上述值。
具体来说,设 \(F\) 的复合逆为 \(G\),欲求 \([x^n]F^{0\dots n}(x)\)。由拉格朗日反演:
等价于求 \([x^{0\dots n}]G'(x/G)^{n + 1}\)。