SDOI 2017 Round 2 大赏
图源 zzq's Blog, 至少我是从那里找到的
这 Round 2 也太毒瘤了 = =
久 等 了.
「SDOI2017」龙与地下城
验题人在多测的情况下总算是写了十行代码?
题目链接
解题思路
首先有一个多项式做法.
构造一个生成函数 $g(x)$, 其 $k$ 次项系数表示掷一次骰子造成伤害 $k$ 的概率, 则
那么 $g(x)^Y$ 的 $A_i$ 到 $B_i$ 次项和即为答案.
对于这个多项式幂函数的计算, 可以用带大常数的 $O(n \log n)$ 多项式 $\exp$ , 或者朴素快速幂 $O(n \log ^2 n)$.
但是有精巧的做法, 此处直接把点值做 $Y$ 次幂就好了.
当年考场 AC 似乎有更为精巧的优化方法, 好像 myy 的论文 <再探快速傅里叶变换> 里有涉及, 以后再说以后再说
对于大数据 如果问什么是大数据, 那就是 FFT 跑不过的数据, 则要用到 中心极限定理.
记 $\zeta_{n} = \dfrac{\bar X - \mu}{\sigma / \sqrt n} = \dfrac{\sum\limits_{i=1}^n X_i - n \mu}{\sqrt {n \sigma ^2 }}$, 根据中心极限定理, 当 $n \rightarrow \infty$ 时, 认为其满足正态分布 $N(0, 1)$.
而正态分布 $N(\mu, \sigma^2)$ 的概率密度函数为
对于 $N(0, 1)$, 带进去可得
具体应用到这道题中, 用自适应 Simpson 计算
就好了, 其中 $a = \dfrac{A_i - Y \mu}{\sqrt{ Y \sigma ^2 }}, b = \dfrac{B_i - Y\mu}{\sqrt{ Y \sigma ^ 2}}$.
代码实现
就这样吧 = =, 只能流下没有数理基础的眼泪.
至今不知道为什么直接大力 Simpson $[L, R]$ 会挂, 取端点做差就对了 = =
可能是因为 $L$ 正负性的问题?
1 | // LOJ #2267 |
「SDOI2017」苹果树
题目链接
解题思路
观察题意可以发现, 关于 $k$ 的限定可以看作免费取一条从根开始的链, 再取 $k$ 个物品, 取儿子必须取至少一个父亲的树形依赖背包. 棘手的地方在于链的处理.
考虑怎么把链干掉. 可以发现, 树上点权都是正的, 如果取一条链, 那么一定从根一直取到叶子. 所以可以枚举叶子, 把树分成链左半部分和右半部分.
再看一遍树的结构, 实际上把树分成了 3 部分
- 链上免费取的部分
- 链左边付费取的部分 (我们把链上付费取的部分归到这里)
- 链右边付费取的部分
所以设 $f(i, j)$ 表示第 $i$ 个节点, 体积为 $j$ 的最大收益, 也就是在计算第 2 部分的答案.
类似地, 设 $g(i, j)$ 表示 DFS 序翻转后, 第 $i$ 个节点, 体积为 $j$ 的最大收益, 也就是在计算第 3 部分的答案.
树上依赖的关系不好处理, 考虑将每个物品数 $a_i > 1$ 节点拆开, 拆成一个物品数为 $1$ 的节点留在原来的位置, 以及一个物品数为 $a_i - 1$ 的节点挂在另外一个节点旁.
(实际上建图的时候并不必要把这个点真的拆开, 在转移的时候额外判断就好了).
最后枚举叶子, 累加第 1 部分, 把另外两部分拼起来即可. (也就是 $f(i, j) + g(i, j-k)$)
注意到对于每个节点的转移, 实际上是一个多重背包, 使用单调队列优化即可.
时间复杂度 $O(Qnk)$.
代码实现
实现参考了 Claris.
有些卡常, 需要把 DP 的二维数组开成一维, 增加缓存命中率.
1 | // LOJ #2268 |
「SDOI2017」切树游戏
题目链接
解题思路
先祭上 猫锟的解题报告.
然后就是动态 DP 了.
此处信息仍具有可减性, 维护数非 0 部分的积, 以及乘积中 0 的个数即可了.
落实到代码中就是那个 Num
了.
还有个可以借鉴的 Trick 就是化简矩阵减小常数了. 直接搬来 immortalCO 的公式.
运用这个 Trick 的时候, 需要额外注意矩阵的运算顺序.
动态 DP 采用树链剖分实现, 时间复杂度 $O(n\log ^ 3 n)$.
最近一群毒瘤看着这个 log^3 不爽, 看来要去学 DDP 的 LCT 实现了
代码实现
1 | // LOJ #2269 |
「SDOI2017」天才黑客
题目链接
解题思路
如果把边看作点, 容易得到这样一个做法:
- 枚举边, 假设当前枚举到的边为 $(a, b)$.
- 找到形如 $(b, c)$ 的边, 两者之间连边, 边权为两者 LCP, 也就是 Trie 树上 LCA 到根的距离.
- 跑 Dijkstra, 对于每个点 $u$, 找到形如 $(a, u)$ 的边更新答案.
但是这个算法有一个明显的问题, 遇到原图中点数度数很大的情况就会连很多边…
考虑如何优化这个过程.
对于 Trie 上一个节点 $u$, 将 $u$ 周围一圈节点按 DFS 序排序, 类似于后缀数组, 设 $h_i = \operatorname{LCP}(s_i, s_{i+1})$, 那么
利用后缀数组中常见套路, 可以利用单调栈求出 LCP 为 $h_i$ 的一段区间, 然后点向区间连边, 区间向点连边即可. 利用线段树优化建图即可.
但是有更加简便的办法. 还是引用 Claris 的博客:
枚举每个 $h_i$ 作为分界线,那么新图中两侧的点均可以通过不超过 $h_i$ 的代价互相访问.
建立一排前缀虚点和后缀虚点然后对应前后缀之间连边即可.
具体地说, 前缀入点 prei
和前缀出点 preo
之间边权分别为 $0$, 每个 prei[i]
向 preo[i+1]
连边, 权值为 $h_i$.
这样连边就以较小的代价做了等效的事情. 后缀同理.
可能还是有些抽象, 不过在代码实现里还是很清晰的. 以及一些入点出点的细节也体现在代码里.
时间复杂度 $O(m \log m)$.
代码实现
你问我为什么执意写树剖求 LCA… 这… 我要是会倍增我不就写倍增了吗 = =
以及边拆为点时, 并不需要额外新增一条边记录初始边权, 直接视作点权在跑 Dijkstra 的过程中更新即可.
1 | // LOJ #2270 |
「SDOI2017」遗忘的集合
题目链接
解题思路
如果把所求和已知交换, 那么这道题就是一道生成函数套路题了.
设序列 $a_i$ 表示元素 $i$ 是否存在于集合 $S$ 中. 那么取 $S$ 中元素之和的方案数的生成函数为
看到乘积不爽. 两边同时取 $\ln$, 得
记 $g(x) = \ln (1-x^i)$, 那么对 $g(x)$ 求导得到
由广义二项式定理, 得
再积分, 得
所以我们就得到了
代入原式, 可以得到
设 $T = ki$, 并交换枚举顺序, 得
至此, 这道题就做完了. 对给定的 $F(x)$ 求 $\ln$ 后莫比乌斯反演即可.
此处并不需要筛出来 $\mu$ 之后 $O(\sqrt n)$ 枚举约数… 直接利用 VFleaKing 反演课件 里的技巧即可.
时间复杂度 $O(n\log n)$.
其实整个过程叫做 “Euler Transform”? 类似的技巧也在 Luogu P4389 付公主的背包 用到过.
代码实现
先滚过去学了 MTT 才写了这道题 = =
1 | // LOJ #2271 |
「SDOI2017」文本校正
题目链接
解题思路
一道毒瘤的字符串匹配题.
假设串 $T$ 被拆分为形如 ABC 的三段, 有一个大力匹配的思路, 即枚举 $3!$ 种情况, 依次判定即可.
ABC
直接用哈希判断两串是否相等即可…
CAB, BCA
以 CAB 为例, 注意到 AB 在 $T$ 中是连续的一段, 枚举 AB 和 C 的断点, 用哈希判断是否和 $S$ 中对应一段相等即可.
BCA 同理.
CBA
开 始 了.
我们先把 $T$ 倒过来再插入 $S$, 得到一个新串 $S_1 T_n S_2 T_{n-1} \cdots S_n T_1$, 那么 CBA 的判断就是在判断得到的新串是否可以被拆成 3 个偶回文串.
可以在新串上枚举一个拆分点, 现在的问题就是: 判断后缀是否构成一个双回文串.
有一个来自
的结论 如果 $s = ab$, $a$, $b$ 都是回文串, 则称 $s$ 是一个双回文串.
如果 $s$ 是一个双回文串, 则存在一种拆分方法 $s = ab$, 使得 $a$ 是 $s$ 的最长回文前缀, 或者 $b$ 是 $s$ 的最长回文后缀.
所以可以用 Manacher 处理出
当前拆分点向右延伸的最长回文串的结束位置, 也就是最长回文前缀, 代码中为
Rpos[i]
.能够到达新串结尾的回文中心集合, 也就是最长回文后缀, 打标记后丢到队列里.
假设当前枚举到的断点为 $i$, 若 $i$ 位置前是一个偶回文串, 依次用最长回文前缀和最长回文后缀判断 $i$ 位置后是否满足限制即可.
BAC, ACB
以 BAC 为例, 如果枚举 C 的位置, 利用上面的经验, 剩下的部分就是一个判断双回文串的问题…
但是有简单一些的办法.
回忆 Case 3 处理问题的过程, Manacher 其实在做一个最大匹配, 也就是说, 此处 BA 两串一定有一者是长度最大的, 利用 KMP 完成这个最大匹配即可. 剩下部分利用哈希判断就好了.
那么对于 ACB 的情况, 真的是字面意思上倒过来就可以了, 注意最后的答案, 先前的哈希值也要翻转. 当然再写一遍也是可以的, 有常数上的优势.
时间复杂度 $O(n)$.
考场上我当然是选择 $O(n^2)$ 的暴力匹配 = =.
代码实现
1 | // LOJ #2272 |
后记
本来想简单地写写, 后来发现自己言简意赅的能力不足 = =
以后尽量少写一点废话吧.