26-01

CF

CF2184G *?

神秘数据结构题。

考虑一个事实,若当前已经满足了 \(\min\{a_{l}, a_{l+1},\cdots, a_{l+d}\}=d\),若再往后扩展一位,到达 \(a_{l+d}\)。若还想有贡献就必须满足:\(\min\{a_{l}, a_{l+1},\cdots,a_{l+d},a_{l+d+1}\}=d+1\),但注意到最小值一定单调不升,但 \(d+1>d\),所以每次询问最多只能有个答案。

因此只需要判断在 \([l, r]\) 中是否存在满足条件的答案即可。然后注意到这个东西显然存在单调性,所以可以二分答案。似乎不能线段树上二分,所以只能做到 \(O(q\log^2 n)\)

CF547E *2800

拜谢 fjy666。

看到包含的子串,容易想到 ACAM 的 fail 树。因此可以先建一下 ACAM,然后考虑每一次询问等同于什么。即查询在以 \(k\) 为根的 fail 树里,有多少个在 \([l, r]\) 区间内的节点。

这东西离线下来应该比较好做。考虑对于每一个节点开一棵线段树,然后再用线段树合并维护个数就行了。

假的没边了,fail 树不能处理有多少个字符串包含 \(s_k\) 作为子串。那似乎就只能考虑按 \(r\) 排序了然后进行一些神秘的维护了。

转化一下,将 \([l, r]\) 的和变成 \([1,r]-[1,l-1]\) 的,这样固定了左端点,好做很多。然后不就在 fail 树上进行 \(+1\) 操作,然后树状数组维护 dfs 序再查询一下就行了。

CF1856E2 *2700

这东西被栋呆放在了他的模拟赛 t1???

E1 的做法很好想,只需要注意到子树内 dfs 序连续与和一定差小积大就可以直接上可行性背包了。但是这样的复杂度为 \(O(n^2)\)

此时考虑优化。

设当前存在 \(n\) 个物品 \(a_i\)\(m=\sum a_i\)。则有一个显然的发现为不同的 \(a_i\) 不超过 \(\sqrt{m}\) 个。

问题便转换成了:有 \(\sqrt{m}\) 个物品,每个物品的重量为 \(a_i\),个数为 \(c_i\)

这个多重背包可以使用二进制优化的方式。将 \(c_i\) 拆成 \(2^0+2^1+\cdots+2^k+x\),其中 \(x<2^{k+1}\),然后重量分别乘上原来的重量,直接跑 0-1 背包就行了。正确性证明感性理解,和倍增应该差不多。

同时,因为这是可行性背包,所以可以使用 bitset 优化转移。单个节点的复杂度变成 \(O(\frac{m\sqrt{m}\log m}{w})\),但是有大神证明了此为:\(O(\frac{m\sqrt{m}}{w})\)。总时间复杂度是 \(O(\sum_{u}\frac{sz_u\sqrt{sz_u}}{w})\) 的。

但是这样仍然通不过该题,因为最坏时间复杂度可以卡到 \(O(n^2)\)。贪心的考虑,如果对于节点 \(u\) 存在一个 \(v\in son_u\) 满足 \(sz_v\ge \frac{sz_u}{2}\),则肯定把 \(sz_v\) 单独放到一组,剩下的放一组。如果不存在这样的 \(v\),则往下递归时每个儿子的子树大小必然减半,看似多带一个 \(\log\) 便可以完成。使用主定理具体分析时间复杂度:

\[T(n)=2T(n/2)+O(\frac{n\sqrt{n}}{w})=O(\frac{n\sqrt{n}}{w}) \]

问题至此解决,不过需要手写 bitset,还是太神了。

AT

AT_abc433_g

最近的一场的 G。

看到子串字眼,很容易想到 SAM。所以可以对 \(S\) 先建出 SAM,然后每次博弈就在 SAM 上走,只能走向有边的地方。然后考虑一下最优策略是怎么样的,因为 SAM 是张 DAG,所以感觉很典的样子。那不就是一个 DAG 上的博弈 dp 吗,参考 CF2137G:Cry Me a River。

AT_abc268_h

fjy666 题单里的。

第一眼感觉很 dp,但是发现似乎不是很行,因为要 \(n^2\) 才可以 dp。容易发现,如果找出了每一个在文本串中的模式串,只需要一个区间选点的简单贪心就可以算出答案。在文本串中查模式串,直接上 ACAM 即可,然后对于每一个走到的节点,只需要找其在 fail 树上深度最小的且为模式串末尾字符的节点即可。因为只要那一个模式串不存在,fail 树上它的子树中的串就都不会存在了。

成螳臂了,区间选点排错序了,应用右端点为第一关键字。

AT_abc439_f

补一下十万年前的 F。

题目相当于问有多少个子序列使得峰值的个数大于谷值的个数。因为要想多出现一个峰值就必然会多出现一个谷值,所以子序列必然形如:

\[x,x+1,x+2,\cdots,x+n,x+n-1,\cdots, x+1, x \]

考虑怎样统计这样的子序列个数。可以 dp 一下,然后用一个 BIT 优化转移。比较显然吧?直接设 \(f_{i, 0/1}\) 表示 dp 了前 \(i\) 个数,子序列是否满足即可。

LG

P4022

远古老题我就不信做不出来。

题目是要求最大的 \(L\) 的,感觉具有一定的单调性,但是不知道为什么 tag 里面没有二分。不管了,先二分答案。因为要处理字串问题,所以考虑建一下 GSAM,然后提出一个朴素的 dp。

\(f_i\) 表示当前询问下前 \(i\) 个字符能得到的最大长度覆盖,\(L\) 表示当前二分到的答案。转移是容易的:

\[f_i=\max_{j\le i-L}\{f_j+i-j\} \]

\(i\) 提出来,得到:

\[f_i=\max_{j}^{j\le i-L\land s[j:i]\in \operatorname{substr}}\{f_j-j\}+i \]

看着这个式子就非常能优化,具体的,容易发现能转移的 \(j\) 是在一段连续的区间里的,且这段区间显然是滑动窗口,所以上个单调队列就可以优化了。现在需要考虑的是,如何将 \(j\) 的左边界找出来,即找到满足 \(s[j:i]\in \operatorname{substr}\)\(\min\{j\}\)。这个东西似乎直接在 GSAM 上面匹配就可以了,如果失配就不断跳 \(\operatorname{link}\),这部分势能是 \(O(n)\) 的,因此总时间复杂度是 \(O(\sum |s_i| \log \sum |s_i|)\)

P10789

只计算点往上爬的情况,考虑一下在每一个点能下滑到子树中的哪些点,然后拆一下贡献。每一个点 \(u\) 的子树中能下滑到的点是有限的,可以先把其它点到 \(u\) 的路径条数算出来,然后对于那些能下滑到的点的路径数就都需要加上 \(u\) 的路径条数。现在考虑一下如何计算到 \(u\) 的路径条数,只需要知道其子树中有多少个 \(d_v-h_v<d_u\) 即可。但要对于每一个点都进行计算哎……

转换一下题意,然后从部分分入手吧。考虑记 \(n\) 次下滑和 \(1\) 次往上跳为一组操作,则显然每一次都是在 \(u\) 的子树中找一个节点往上跳到一个 \(d_v<d_u-h_u\) 的点。显然这个 \(d_x-h_x\) 是单调递减的。然后考虑去数数,发现每一个点 \(u\) 在经过这样一组操作之后必然会在 \(1\sim u\) 的路径上,而且是一个连续的区间。所以,贡献的计算应该是有迹可循的。因此似乎可以从上往下 dfs,然后对于每一个点算上一组操作会从那一个祖先来。但这很不好做。链有什么启发意义吗?

现在倒是有了一个 \(O(n\log^3 n)\) 的做法,即 dsu on tree + 树剖,但怎样去 \(\log\) 啊!dsu on tree 是必要的吗?

神秘启发式分裂。显然我的计算贡献的转移式子并没有问题,但这个启发式分裂又是什么鬼。哦,好像就是 dsu on tree。原来可以消掉一个 \(\log\),只需要维护深度就行了,这样就不用树链剖分了。然后就可以按照上面的转移进行 dp 就行了,线段树实现区间 \(A_i\times B_i\) 优化一下。

但是!在 dp 往下转移的过程中是难以维护子树中节点对祖先节点的贡献的。考虑先预处理一下以每一个节点 \(u\) 为根时其子树中的节点对于不同深度的 \(u\) 的祖先的贡献系数,这可以线段树合并处理出来。

你他妈觉得我会写吗?

P7409

期末考前还在做 SAM /ll/ll。

考虑在往 SAM 中插入每一个字符时记录一下这个 \(\operatorname{endpos}\) 对应着的状态。然后对于每一次询问,直接对被询问到的点建出虚树,跑一下树形 dp 算 \(\operatorname{LCA}\) 的贡献就行了。

P2463

拜谢 fjy666。

将题目简单转化一下,将每一个模式串的相邻两数作差,则显然我们需要求的便是最长的公共子串。

看到子串字眼,想到建出广义 SAM。然后对于每一个状态,标记一下是否每一个模式串中都出现了。最后在 \(\operatorname{link}\) 树上从下往上传递 \(tag\),如果 \(tag=n\) 那么该状态就会有贡献。可以用 bitset 维护的对吧?不想写线段树合并了。

P3181

拜谢 fjy666。

考虑对于第一个字符串建出 SAM,然后因为任意一个在第一个串中的子串都可以在 SAM 中找到。预处理一下每一个状态的 \(\operatorname{endpos}\) 集合的大小。接着直接对第二个串匹配,如果没有找到就不断跳 \(\operatorname{link}\) 直到找到存在的转移边,然后长度乘上 \(\operatorname{endpos}\) 集合的大小就行了。

秒啊!建在同一个 SAM 里,然后设第一个串的状态 \(|\operatorname{endpos}|=sz_1\),第二个串的为 \(sz_2\)。则每一个位置的贡献就是:

\[sz_1\times sz_2\times (\operatorname{maxlen}-\operatorname{minlen}+1) \]

原来这东西是广义 SAM 板子啊。用个假的广义 SAM 草过去拉到。

P4070

拜谢 fjy666。

咋做啊?对 SAM 的理解还是太不深刻了。看来我上一题的结论是对的啊。为什么为什么为什么为什么????哦哦哦哦哦。\(\operatorname{endpos}\) 大法好。

P2408

拜谢 fjy666。

SAM 板子,我不会。考虑在 link 树上查一下子树大小,然后乘上 \(maxlen - minlen+1\) 再求和就行了。

**,这是 DAG 上 dp 的板子。

P14963

对于两个将要进行比较的串串 \(A\)\(B\),考虑其能否都变成 \(C\)。即是否满足 \(A\rightarrow C\)\(B\rightarrow C\)。如果满足,那么显然是可以相等的,反之则不行。而不妨令 \(C\) 为将所有 \(a_i=a_{i+1}\)\(a_i\)\(a_{i+1}\) 都删掉之后得到的串串。

这个东西可以用线段树维护 hash 实现,pushup 是简单的,将偶数个的连续子串串不计入 hash 值,奇数个的连续子串则计入一个就行了。

假了,操作会有后续影响,即操作完之后可能形成新的 \(a_i=a_{i+1}\) 的子串。

原来又是随机值异或哈希,但这个东西具有交换律,所以显然不行。我们需要的运算为:

  • 满足结合律
  • 不满足交换律
  • 与自身运算等于单位元

这启示我们用矩阵。(完了,不会矩阵)

定义 \(2\times 2\) 的矩阵,其单位元为:

\[\begin{pmatrix} 1 & 0\\ 0 & 1\\ \end{pmatrix} \]

而我们定义的矩阵则形如:

\[\begin{pmatrix} a & b\\ c & d\\ \end{pmatrix} \]

我们需要的是该矩阵乘自己等于单位矩阵,因此有:

\[\begin{pmatrix} a & b\\ c & d \end{pmatrix} \times \begin{pmatrix} a & b\\ c & d \end{pmatrix} = \begin{pmatrix} 1 & 0\\ 0 & 1\\ \end{pmatrix} \]

展开得:

\[\begin{pmatrix} a^2+bc & ab+bd\\ ac+cd & bc+d^2 \end{pmatrix} \]

故:

\[\left\{ \begin{aligned} a^2+bc=1\\ ab+bd=0\\ ac+cd=0\\ bc+d^2=1\\ \end{aligned} \right. \]

\(a\)\(b\)\(c\)\(d\) 都不等于 \(0\),解一下就得到了,\(a=-d\)。故 \(bc=(1+a)\times (1-a)\) 就行了。所以令 \(b=(1+a)\)\(c=(1-a)\),则构造处理一个如下的矩阵:

\[\begin{pmatrix} a & 1+a\\ 1-a & -a\\ \end{pmatrix} \]

然后就可以用线段树维护矩阵乘法了。6,普通自然溢出被卡了,得随机赋权。

P4211

无语,场上被神秘 trick 创飞,补一下削弱版。

完了,削弱版都不会,糖丸了。

考虑离线下来然后扫描线,可以把一个点的答案转换成 \(\sum_{i=0}^{r}depth_{\operatorname{LCA(i, z)}}-\sum_{i=0}^{l-1}depth_{\operatorname{LCA(i, z)}}\)。然后用些数据结构去维护答案。哦,注意到 \(depth_u\) 等价于 \(u\) 到根的距离 \(+1\),所以可以把 \(depth_{\operatorname{LCA(u, v)}}\) 变成 \(\operatorname{dis}(0, \operatorname{LCA(u, v)})+1=\frac{\operatorname{dis}(0, u)+\operatorname{dis}(0, v)-\operatorname{dis}(u, v)}{2}+1\)。分子中的前两项都是好求的,只需要处理第三项就行了。怎么处理呢?

卧槽!在权值全为 \(1\) 的树中,\(depth_u\) 等同于 \(u\) 到根的路径权值和。

再写一下王晶模拟赛 t1 的解法吧,本质同源。

题面

给定一棵 \(n\) 个节点的树,第 \(i\) 条边连接点 \(x_i\)\(y_i\),带有 \(z_i\) 的正整数权值,并定义点和路径之间的距离函数 \(\operatorname{dis}(x,\operatorname{path}(u,v))\) 为点 \(x\) 到路径 \((u,v)\) 上最近的点的距离。

对于一个点集 \(S\),定义其中选出两个不同的点组成无序二元组所构成的集合为 \(T_S\),形式化即 \(T_S=\{(u,v)\mid u,v\in S, u<v\}\),保证点集中不存在相同的点。

定义点和点集的函数 \(F(x,S)=\sum_{(u,v)\in T_S} \operatorname{dis}(x,\operatorname{path}(u,v))\),即到内部每一对路径的距离和。

给定 \(m\) 个点集 \(S_1,S_2,\dots,S_m\)\(q\) 次询问给定 \(x,l,r\),求 \(\sum_{i=l}^r F(x,S_i)\)。由于答案可能很大,请对 \(998244353\) 取模。

做法

到路径上的最短距离很难受,于是直接拆。将 \(\operatorname{dis}(x,\operatorname{path}(u,v))\) 变成 \(\frac{\operatorname{dis}(x, u)+\operatorname{dis}(x, v)-\operatorname{dis}(u, v)}{2}\)。于是询问就变成了:

\[\sum_{i=l}^{r}\frac{(|S_i|-1)\times\sum_{u\in S_i}\operatorname{dis}(u, x)-\sum_{u, v\in S_i}\operatorname{dis}(u, v)}{2} \]

考虑分子中前面的那个式子怎么求。钦定 \(1\) 为根,将其再按照第一次拆分的方法转换成:

\[\sum_{u\in S_i}(\operatorname{dis}(u, 1)+\operatorname{dis}(x, 1)-\operatorname{dis}(\operatorname{LCA}(x, u), 1))\times (|S_i|-1) \]

这就可以直接按照上面的套路做了,即直接上树剖+线段树维护。

接着考虑分子中的第二个式子的求法。对于每一个集合 \(S_i\),预处理出这个东西,然后再套用上面的套路跑一遍就行了。

码量怎么那么大!!!码力不够。终于过了……

P6240

恶补一下算法,学猫树分治。

非常板的题目,区间 01 背包。因为具备区间可加性,且不待修,因此直接上猫树分治,进行 \(O(t)\) 合并就行了。因为 01 背包本质是 \(\{\max,+\}\) 卷积,所以可以 \(O(t)\) 合并。

故算法流程为先分治两边节点的背包,然后再进行 \(q\) 次合并。总时间复杂度为 \(O(nt\log n+qt)\)

P10795

考虑给它拆贡献。

\(t_i\) 从大到小加入节点,这会形成一个森林。而对于每一个新加入的点,\(u\) 能有贡献当且仅当在 \(i\)\(j\) 和它在的同一棵树中。然后它的贡献就等于在 \(u\) 的子树中 \(v_i\le v_u\) 的数量乘不在 \(u\) 的子树中 \(v_j\ge v_u\) 的数量再加上反过来的结果。

因此现在需要动态地维护一个森林。LCT 似乎很可做,但应该有更简单的方法。Wait,这个东西似乎是动态二维偏序啊,做你妈。

哦,似乎不用这么麻烦,有特殊性质使我受到启发,直接在将 \(u\) 加入的时候进行查询就行了。用线段树合并维护。

P11622

挺好的题。

考虑将询问离线下来,然后进行扫描线。用平衡树维护现在正在询问中的数,对于 \(x<\lfloor \frac{a_i}{2}\rfloor\) 的就变成 \(a_i-x\),否则不变,这东西可以打 tag 实现,然后合并的时候用 FHQ-Treap 有交合并就行了。

P1850

认真补期望 dp。

首先可以拿 floyed 预处理一下最短路。

\(f_{i, j,0/1}\) 表示 dp 了前 \(i\) 个数,当前换了 \(j\) 个教室且是否在该位换了教室的最小期望。由期望的线性性质,可以直接将两个期望值相加得到最终的期望。所以转移如下:

\[f_{i, j, 0}=\min\{f_{i-1,j,0} + e_1,f_{i-1,j,1} + e_2,f_{i-1,j-1,0/1}+dis\times (1-k_i)\} \]

\[f_{i,j,1}=\min\{f_{i-1,j-1,0}+e_1,f_{i-1,j-1,1}+e_2\} \]

最后取 \(\min\) 就行了。漏了一点点情况,不过没有关系。

P5384

简单的,倍增往上跳到 \(k\) 级祖先那里,然后再用 dsu on tree 统计一遍就行了。

P5987

你他妈那么喜欢 hash 是吗?

横纵坐标相对独立,于是只需要解决横纵坐标互相的交集,然后相乘即为答案。

然后因为每一条线段的状态都是一定的,即要么被一个线段覆盖,要么被其补集覆盖。此时,如果线段数量 \(\le64\),那么可以将每一小段看成 \(2^i\),然后进行异或操作,最后看相同的值的最大出现次数。但题目值域太大了,所以上随机赋权异或 hash 就行了。

P5356

拜谢 critnos,\(O(n\sqrt{n})\) 还是太强了,没听懂。

这东西是区间加,树套树维护不了,于是直接上一个分块。

考虑到这个第 \(k\) 小是单调的,所以直接二分一下答案。接着需要判断的是 \([l, r]\) 中有多少个比二分到的阈值 \(x\) 小,如果这个数量 \(\ge k\) 就收缩右边界,否则收缩左边界。

接下来考虑如何判断这段区间有多少个数,因为每一块都排好序了,所以仍然二分就行了。

***,不想调。

P3350

拜谢 critnos。

因为 \(n\times m\le 2\times 10^4\),所以 \(\min(n, m)\le \sqrt{n\times m}\)。因此,我们可以考虑按网格上较短的那一边将网格从中间劈开,然后枚举中间的那 \(\sqrt{n\times m}\) 个点,使用一个 dij 计算其到两边的点的最短路。因为散布在这一列两边的点之间的最短路必然经过这列上的点,所以可以直接计算出询问的答案。然后再直接递归地计算两边的答案即可。

用主定理可以得到时间复杂度为 \(O(nm\sqrt{nm}\log{nm})\)

P7581

不是哥们,这种紫真的没有思维难度吧?

离线下来,将 \(k-son\) 的定义转换成深度之差。假设现在 dfs 到点 \(u\),那么对于所有的 \(v\in son_u\),其对答案的贡献为以 \(v\) 根的子树中深度为 \(depth_u+k\) 的节点的距离之和。同时,子树与子树之间仍然会产生贡献,设以 \(x\) 为根的子树中深度为 \(y\) 的点到 \(x\) 距离之和为 \(sum_{x, y}\),则还要加上:

\[\sum_{i\in son_u}\sum_{j\in son_u}^{i\neq j}sum_{i, depth_u+k}\times sum_{j, depth_u+k} \]

用线段树合并维护这个东西就行了。

哦哦哦哦哦哦,边带权……

他妈的,又被卡空间了!!!要线性空间的科技……

P9089

被蓝题创飞了。

只会 \(n^2\) 做法了怎么办?建完 ACAM 后枚举每一个字符串的贡献。具体为枚举以 \(i\) 结尾的子串个数,可以直接在 ACAM 上跑,然后判断在 fail 树上是否为末尾字符。但是因为要计算子串个数,需要枚举上一个转移点,所以仍然需要 \(n^2\),怎么办?

wait,这个转移点显然是连续的吧?上个前缀和优化 dp
应该就行了。完了,发现没法去重……

有点妙。后缀不好处理,所以给所有的字符串都翻转一下,再建 ACAM,这样一个子串是合法的当且仅当能够在 ACAM 一直走且除起点外不经过 \(0\)。这就意味这该子串能够被拆分成数个在 ACAM 上的前缀。于是去重便很简单了:

\(g_u\) 表示 \(u\) 在 ACAM 上的最短前缀(即不断跳 fail 后的最小深度),\(f_i\) 表示在翻转后以 \(i\) 为结尾的合法子串个数。则:

\[f_i=f_{i-g_i}+1 \]

最后答案便是:

\[\sum_{i=1}^{n}\sum_{j\in s_i} f_j \]

P3521

容易发现以点 \(u\) 为根的子树的最小逆序对数是不会对以 \(fa_u\) 为根的逆序对数有影响的。考虑到逆序对数不好直接维护,只能通过枚举子树中的节点,然后开一个权值线段树查询,这个可以线段树合并维护一下。

注意到是二叉树,很有启发意义,因为每一个节点只有两个儿子。因此想到类似于 dsu on tree 的,每次都暴力枚举轻儿子,然后在重儿子的权值线段树中查询并统计逆序对个数。如果小于交换的逆序对数量,就不交换,否则交换。

这样的时间复杂度为 \(O(n\log^2 n)\),因为 dsu on tree 和线段树合并各带一个老哥。

P4052

建一下 ACAM,然后直接 dp。

\(f_{i, j, 0/1}\) 表示 dp 到 ACAM 上的节点 \(i\),此时生成的文本长度为 \(j\),且文本中是否已经出现了模式串。转移是容易的。

P3311

感觉很像数位 dp 加一个 ACAM。同时注意到该题中不存在于字符集中的字符和 P2444 差不多。

考虑一个朴素的数位 dp。只需要设 \(f_{i, j}\) 表示为 \(i\) 位且最高位为 \(j\) 的方案数,如果在记忆化搜索的时候搜到了存在于模式串中的子串,就直接返回 \(0\)。这个东西显然可以在 dfs 的同时在 ACAM 上走,然后判断是否存在相同的子串。但是这会引出一个问题,那就是如果记忆化直接加上答案的话,很可能加上的 \(f_{i, j}\) 中存在答案使得可以与原有部分拼成存在于模式串中的子串。

得转换一下状态了,\(f_{i, j}\) 表示以点 \(i\) 开头且长度为 \(j\) 的答案。因为要在 ACAM 上走到点 \(i\),则它到根的点应该就都走过了,所以不能存在结尾的节点。(描述很抽象)

以此想到一个由儿子向父亲转移的过程。则可以刷表得。

不会了。

发现自己的状态稍微欠缺啊!只不过纯循环 dp 需要加上一维是否贴着上界。即 \(f_{i, j, k\in \{0,1\}}\) 表示 dp 到节点 \(i\) 且考虑了前 \(j\) 位且有没有贴着上界。然后思考转移:

  • \(k_1=1\land k=1\land son_{n_i} \text{在} \operatorname{fail} \text{树上不存在模式串的结尾}\),则 \(f_{son_{n_i}, j + 1, k_1}\leftarrow f_{i, j, k}\)
  • \(k_1=0\land k=1\land son_{n_i} \text{在} \operatorname{fail} \text{树上不存在模式串的结尾}\),则 \(f_{u\neq son_{u_i},j+1,k_1}\leftarrow f_{i, j, k}\)
  • \(k_1=0\land k=0\land son_{n_i} \text{在} \operatorname{fail} \text{树上不存在模式串的结尾}\),则 \(f_{u, j+1, k_1}\leftarrow f_{i, j, k}\)

然后直接做就行了,唯一需要用 ACAM 处理一下是否在 \(\operatorname{fail}\) 树上存在模式串的结尾。

有一点细节。

P8796

显然可以开 \(26\) 棵 FHQ-Treap 分别维护每种字母的答案。然后每次操作就按 \(size\) 分裂一下,再合并即可。

傻了,FHQ_Treap 的普通 merge 无法维护有交合并,因此要用双老哥的有交合并,这是什么奇技淫巧?学到了。。。即每次将子树大将小的合并向大的,暴力将小的那棵树按大的那棵树的权值分裂成两半再分别合并即可。

P1600

先只考虑从儿子节点跑上来的情况。假设现在计算点 \(u\) 的答案,在其子树中点 \(v\) 出发的人有贡献,当且仅当:

\[depth_v-depth_u=w_u \]

整理一下得到:

\[depth_v=w_u+depth_u \]

这就比较好维护了,显然可以 dsu on tree 做吧?开一个桶记录一下 \(depth_i\) 的值就行了。好像不是很好维护的样子,应为如果 dfs 完重儿子之后是不好删除状态的,但我们需要在 dfs 完点 \(u\) 后将 \(depth_{lca_v}\ge depth_u(v\in tree_u)\)\(v\) 删除状态,所以平常的 dsu on tree 有点难做。其实可以使用权值线段树维护 \(depth_{lca_v}\),然后线段树合并一下可以做到双老哥。

如果是从父亲跑到儿子的就 swap 一下,然后就是:

\[len_v-depth_v+depth_u=w_u \]

整理可得:

\[len_v-depth_v=w_u-depth_u \]

即:

\[depth_s+depth_t-2\times depth_{\operatorname{LCA}(s, t)}-depth_t=w_u-depth_u \]

\[\therefore depth_s-2\times depth_{\operatorname{LCA}(s, t)}=w_u-depth_u \]

所以还要维护一个相减的值。突然发现不用线段树合并,直接拿桶维护就行了,只需要提前把 \(\operatorname{LCA}(s_i, t_i)\) 标记一下,然后重儿子的信息上传时将其删除即可。同时因为可能 \(\operatorname{LCA}(s_i, t_i)\) 处会有重复计算的答案,所以需要在 dfs 完一个点后进行去重操作,可以和上面的删除操作放在一起进行。

原来可以用线段树合并加差分维护每一条路径上的答案。

P14363

补一下 CSP-S 真题。

考虑对文本串建立两个 ACAM,一个存储替换之前的串串,一个存储替换结果,然后对于每一个模式串都进行两次匹配。第一次匹配把匹配到的结果的编号都标记一下,然后第二次对于替换结果进行匹配,如果匹配成功就查询一下编号是否被标记,被标记的串串说明可以替换成功。

\(u\) 在 ACAM 上的 \(\operatorname{fail}\) 指针指向 \(\operatorname{fail}_u\)\(s_i\) 的长度为 \(|s_i|\)。则容易观察到若节点 \(u\) 可以替换成功,则对于所有的 \(|\operatorname{fail}_u|\ge \text{替换串不相等的长度}\)\(\operatorname{fail}_u\) 均满足条件。

考虑如何计算这个东西,发现在线不好算,于是想到离线。把 \(\operatorname{fail}\) 树建出来,然后在树上进行 \(dfs\),同时维护一个树状数组,此时问题就转变成二维数点了。

怎么感觉代码实现有点复杂?

成废柴了,这都啥呀!直接对于每一个 \(S_1\rightarrow S_2\) 的模式串找出最长公共前后缀,则有 \(S_1=pAs\)\(S_2=pBs\),将其转换成 \(p?AB?s\) 然后插入 ACAM,对于文本串同理,跑多模式串匹配即可。

posted @ 2026-02-01 09:18  Tomwsc  阅读(5)  评论(0)    收藏  举报