随机乱做 Part 6
- 「CF799F」Beautiful fountains rows(随机化 + 前缀和)
- 「CF908G」New Year and Original Order(数位 DP + 拆贡献)
- 「SDOI2014」数数(AC 自动机 + DP)
- 「CF1743F」Intersection and Union(DP + 矩阵乘法 + 线段树)
- 「SDOI2016」储能表(数位 DP)
- 「ICPC World Finals 2018」绿宝石之岛(概率期望 DP)
- 「CF870E」Points, Lines and Ready-made Titles(建图 + 二分图)
- 「CF1749E」Cactus Wall(最短路 + 性质 + 01BFS)
- 「CF1749F」Distance to the Path(树上差分 + 树状数组)
- 「CF547E」Mike and Friends(AC 自动机 + fail 树 + 树状数组)
「CF799F」Beautiful fountains rows(随机化 + 前缀和)
很神奇的一道题……
首先同样可以给每一行的 1
分配一个随机权值,如果一行中某个区间内有奇数个,那么区间内的异或和就不为 \(0\),否则就是 \(0\)。
显然为 \(0\) 比有值好处理得多。于是考虑转换限制,即有奇数个 1
的时候区间异或和为 \(0\),有正偶数个 1
的时候区间异或和为 \(1\)。这样也可以处理掉区间内没有 1
的情况。
怎么做到呢?可以让 \(l_i\) 不异或随机权值,并且把询问的 \(a\leftarrow a+1\),即区间变成 \((a+1,b)\) 。证明考虑分类讨论 \(a\) 这个位置是不是 1
。
很明显可以把所有行某一列的信息整合起来,即记一个 \(x_i\) 表示所有行第 \(i\) 列的权值异或和,\(s_i\) 表示 \(x\) 的前缀异或和。那么 \((a,b)\) 合法就等价于 \(s_a=s_b\)(因为我们已经把区间的左端点 \(+1\))。
这个可以通过开一个 map 实现求解。
全部为 \(0\) 的情况可以在最后减掉。
代码:https://pastebin.ubuntu.com/p/yRZFy3NtHQ/。
「CF908G」New Year and Original Order(数位 DP + 拆贡献)
考虑把每一个数值的答案拆开之后再加起来。
因为最后的贡献中数位是不降的,所以可以考虑把贡献拆成不超过 \(9\) 个形如 \(111...1\) 的形式。
关键一步:第 \(i\) 个拆开的数中 \(1\) 的个数其实就是 \(\ge i\) 的数位个数。
比如,\(125=111+11+1+1+1\)。
那么就可以先枚举每一个数码,设 \(dp_{i,j,0/1}\) 表示已经确定了前 \(i\) 位,有 \(j\) 位 \(\ge\) 当前枚举的数码,是否顶着上界的方案数。转移枚举这一位填什么即可。最后加入答案的时候枚举贡献中有多少个 \(1\),数值乘上方案数就是最终贡献。
代码:https://pastebin.ubuntu.com/p/W5NsS5fn6t/。
「SDOI2014」数数(AC 自动机 + DP)
设 \(f_{i,j,0/1}\) 表示已经填了前 \(i\) 位,到达了节点 \(j\),是否顶上界的合法数字个数。
转移的话可以直接枚举下一位,或者新开一位。
注意如果一个节点 \(fail\) 树上的祖先有不能走的,那么它也不能被走到。因为 \(fail\) 的含义就是前缀的一个后缀。
代码:https://pastebin.ubuntu.com/p/3HgBDkK3mK/。
「CF1743F」Intersection and Union(DP + 矩阵乘法 + 线段树)
考虑对每一个位置算贡献,即求出它在最终集合中的操作序列方案数。
这个可以用简单 DP 求:设 \(f_{i,0/1}\) 表示考虑了前 \(i\) 个集合,当前位置在不在当前的答案集合中的方案数。
转移:
- 若第 \(i+1\) 个集合包含了当前位置,那么:
- \(f_{i+1,0}=f_{i,0}+f_{i,1}\);
- \(f_{i+1,1}=f_{i,0}\times 2+f_{i,1}\times 2\)。
- 若第 \(i+1\) 个集合没有包含当前位置,那么:
- \(f_{i+1,0}=f_{i,0}\times 3+f_{i,1}\)。
- \(f_{i+1,1}=f_{i,1}\times 2\)。
如果我们对于每一个位置都 \(\mathcal{O}(n)\) 再算一遍,未免有些太浪费了。
因为每次转移的系数是一样的,所以考虑把操作写成 \(2\times 2\) 矩阵的形式,使用一棵线段树维护区间矩阵乘积。矩阵中的 \(A_{i,j}\) 表示 \(f_{*,i}\to f_{*,j}\) 的转移系数。
扫一遍每个位置,在当前位置是某个区间的左端点的时候将这个区间对应的线段树下标的矩阵改成包含当前位置的转移矩阵,是某个区间右端点 \(+1\) 的时候将矩阵改成不包含当前位置的转移矩阵。
因为矩阵乘法满足结合律,所以我们只需要知道当前位置是否在第一个区间中就可以算出最终的答案。
代码:https://pastebin.ubuntu.com/p/XnPDBTxC2H/。
「SDOI2016」储能表(数位 DP)
考虑直接计算 \(i\oplus j\ge k\) 的数量以及所有满足条件的 \(i\oplus j\) 的和。
这个可以直接数位 DP:设 \(f_{i,0/1,0/1,0/1}\) 和 \(g_{i,0/1,0/1,0/1}\) 分别表示考虑到第 \(i\) 位,是否顶到了 \(n,m,k\) 的上界,满足条件的方案数以及 \(i\oplus j\) 之和。转移直接枚举下一位是什么即可。
可以先求出 \(f\) 数组,然后再去算 \(g\) 数组。因为 \(g\) 的转移是依赖于 \(f\) 的。
这种记两种 DP 数组的方法很巧妙!
代码:https://pastebin.ubuntu.com/p/5KPw9hQ3fN/。
「ICPC World Finals 2018」绿宝石之岛(概率期望 DP)
首先,所有的操作个数有 \(n(n+1)\dots(n+d-1)\) 种,即 \(\frac{(n+d-1)!}{(n-1)!}\)。
考虑一种最终局面 \(a_i\),其中 \(a_i\) 表示第 \(i\) 个人新拿的绿宝石个数,\(\sum a_i=d\),得到它的操作序列个数。为 \(\frac{d!}{\prod a_i!}\prod a_i!=d!\),前者是可重集排列,后者是每个人当前可以选择手上哪一个宝石分裂。因此,我们可以得出结论:每种最终局面的对应方案数是相同的!
接下来,对于 \(a_i\) 按层从小到大进行 DP:设 \(f_{i,j}\) 表示 \(i\) 个人,此时 \(\sum a=j\) 的方案数,\(s_{i,j}\) 表示 \(i\) 个人,此时 \(\sum a=j\) 的所有前 \(r\) 大的 \(a\) 的和。
转移:
可以解释为在这 \(i\) 个人中选择 \(k\) 个出来 \(+1\)。
最终答案也是好求的。
注意要开 long double
!
代码:https://pastebin.ubuntu.com/p/sNHqt42gnw/。
「CF870E」Points, Lines and Ready-made Titles(建图 + 二分图)
建一个二分图,左部点是所有横坐标,右部点是所有纵坐标,一个点 \((x,y)\) 就相当于将左边的点 \(x\) 和右边的点 \(y\) 连起来。
考虑对于每一个连通块分开考虑,再利用乘法原理将它们的答案乘起来。
如果一个连通块是一棵树,那么设 \(cnt\) 为其点数,则答案为 \(2^{cnt}-1\);否则答案为 \(2^{cnt}\)。
考虑每个左部点如果是 \(1\),就相当于有一条竖直的直线;右部点如果是 \(1\),就相当于有一条水平的直线。如果是一棵树,除了全都是 \(1\) 的情况都可以出现;如果不是一棵树,那么所有情况都能出现。
代码:https://pastebin.ubuntu.com/p/D2W3T9ThgV/。
「CF1749E」Cactus Wall(最短路 + 性质 + 01BFS)
一个重要的观察就是:条件等价于存在一条从第一列到最后一列的仙人掌路径。这条路径中每个点应该在前一个点的右上角或右下角。证明显然。
所以直接建图跑 01BFS / Dijkstra 即可。
因为是口胡的,所以没有代码。
「CF1749F」Distance to the Path(树上差分 + 树状数组)
考虑如果一个点被一条路径贡献到,那么它肯定是在某个节点的子树中,或者是距离两端点的 LCA 不超过 \(d\)。
我们可以将 LCA 向上的 \(d\) 个祖先也加入到这条路径的贡献中。
因为 \(d\le 20\),所以我们求答案时暴力跳祖先算贡献。如果一条路径对一个点有贡献,那么跳祖先的时候肯定会和这条路径有交点。因为不能重复计算,所以我们需要找到一个不会算重算漏的点。我们给每个点设一个标记,令每个路径上的点标记为 \(d\),LCA 的第 \(i\) 个祖先的标记为 \(d-i\)。那么当我们跳到第 \(j\) 个祖先的时候,如果路径某个点的标记也为 \(j\),说明这个点可以被这条路径贡献到。但如果直接这样算可能会算漏,需要将 LCA 的第 \(i\) 个祖先同时标记上 \(d-i-1\),这是为了避免在第 \(i+0.5\) 个祖先处相交的情况。画个图可能更清晰。
原路径的贡献可以树上差分 + 树状数组,LCA 祖先的贡献可以直接打标记。
代码:https://pastebin.ubuntu.com/p/Whq6nSh6f8/。
「CF547E」Mike and Friends(AC 自动机 + fail 树 + 树状数组)
首先套路地建出 AC 自动机和 fail 树,然后把询问差分成两个前缀。
每扫过一个字符串就将走过的节点权值全部 \(+1\),查询就相当于查某个字符串结尾节点的子树内节点权值和。
这个可以 dfs 序 + 树状数组维护。