2014多校6 Another Letter Tree
2014多校6 Another Letter Tree
点分治做法
就裸地离个线,放到点分治上,从每个根开始,维护 \(dp_{u,l,r}\) 表示这条链匹配了序列中 \([l,r]\) 的部分
注意dp数组要一正一反,俩家伙一个含根一个不含
查询要合并两个dp数组,但是只需要知道 \(dp_{1,|s_0|}\) ,因此合并复杂度是 \(O(|s_0|)\) 的
最终复杂度,处理为 \(O(n\log n|s_0|^2+q|s_0|)\)
树剖线段树做法
类似上面的dp,线段树维护即可
问题1
需要存正反!
然后你发现内存从中间裂开!!
正反分两次,离线跑两次就可以了啊啊啊啊
问题2
如果直接查询合并,合并两个dp数组复杂度为 \(O(|s_0|^3)\)
查询复杂度为 \(O(q\log ^2n|s_0|^3)\)
妙啊!!比 \(n^2\) 还大!!
所以最后不能合并dp数组,而应该直接累加到答案数组上
问题3
没错现在我们的复杂度为 \(O(q\log ^2n|s_0|^2)\)
依然大得令人无法忍受
但是没想到吧,数据全部都是链,树剖是 \(O(1)\) 的
优化:查询重链时,只有最后依次是在链上查询 \([l,r]\) 都在中间的,而对于直接跳到top的部分,可以预处理出来
算上线段树的预处理,这样总复杂度就是 \(O(n|s_0|^3+q\log n |s_0|^2)\)
并查集做法
把问题拆成查询两条 \(u\) 到它的祖先 \(v\) 的答案
每个节点存储一个dp矩阵,用带权并查集维护
具体方法是:将询问按照 \(\text{LCA}\) 深度逆序排序后,每次查询一直将 \(u\) 合并到 \(v\) 为止
复杂度为 \(O(n\alpha(n)|s_0|^3)\) ,理论上来说,这个转移矩阵应当很稀疏,乘法应该很快,但是实际常数比较大
1 |
|
\[ \ \]
伪矩阵求逆做法
同样的,把问题拆成查询两条 \(u\) 到它的祖先 \(v\) 的答案(不包含v)
以从 \(v\) 到 \(u\) 的字符串为例,设 \(dp_u\) 为 \(u\) 的祖先链的dp矩阵,我们要求的部分答案是 \(x\)
则 \(dp_v\cdot x=dp_u, x=\frac{dp_u}{dp_v}\)
一般来说,矩阵求逆是一个很难实现的东西
但是发现对于一种 \(dp\) ,它的矩阵一定是一个上/下对角的矩阵
我们需要求出矩阵第一维为1或者第二维为 \(|s_0|\) 的部分
如果暴力求,可以看做求解一个 \(|s_0|\) 元的线性方程组,可以用高斯消元在 \(O(|s_0|^3)\) 时间内求解
而实际上,这个线性方程组是含拓扑序关系的,任何一个含拓扑关系的线性方程组求解是不需要高斯消元的
而且这个问题列出的方程矩阵就已经是上对角矩阵了
所以写出来就是容斥吧
tips: 预处理部分一次只插入一个字符,复杂度为 \(O(n|s_0|^2)\) (也可以认为是稀疏矩阵乘法)
查询部分求解线性方程复杂度为 \(O(|s_0|^2)\) ,合并答案复杂度为 \(O(|s_0|)\)
因此复杂度为 \(O((n+q)|s_0|^2)\)
Code: 注意两种dp共用了一个数组
1 |
|