ZR24 Summer A Day3 | String

后缀树

本质就是对于所有后缀建立 Trie 之后,提取所有关键点建立压缩 Trie,也就是所有尾节点为关键点的虚树。这样子树的大小是 \(O(n)\) 的。当然这只是一个理解,实际肯定不是按照上述方法建立的,不然第一步就爆了,其实直接建立 SAM 就可以得到后缀树了。

后缀树上两个串的最长公共前缀是它们的 lca。可以和 SA 的 LCP 用区间 height 最小值的结论结合起来看,就等价于树上一堆点的 LCA 就是 dfn 最小点和 dfn 最大点的 LCA。

P6727 [COCI2015-2016#5] OOP

给出一个由 \(n\) 个只有小写字母的字符串 \(S_i\) 构成的序列。
\(m\) 次询问,每次给出一个恰好带通配符的询问串 \(T\),求这个串能与多少串 \(S_i\) 匹配。通配符可以替换为空串或者某个串。\(\sum len \le 3\times10^6,n\le 10^5\)

\(T=A*B\),其中 \(*\) 为通配符。

我们就是要找到有多少 \(S_i\) 的前缀为 \(A\),后缀为 \(B\),长度 \(\ge \lvert T\rvert-1\)

需要前后缀都出现,于是对于正反串建立 Trie,前后缀要求就转化为子树 dfs 序范围,然后又要求长度,于是就是三维偏序了。时间复杂度 \(O(n\log^2n)\)

还有一个解法,我们对于前缀走 Trie,假设走到了节点 \(u\),对于 \(u\) 的子节点寻找有无后缀哈希值在子节点中出现。这个直接离线 dfn 序扫描线,由于满足可减性可以不用线段树,直接 \([l,r]=[1,r]-[1,l-1]\) 就行了。用 std::map 保存一下哈希值就可以做到 \(O(n\log n)\) 了。这个算法能省去一个 \(\log\) 的原因是第一个算法只在尾节点处产生贡献,而这个是在串的每个位置都产生一个后缀信息的贡献,这样子就省去对于长度的约束了。

QOJ6842.Popcount Words

有一个无限长的字符串 \(s\),其中 \(s_i=\rm popcount(i) \bmod 2\)

给定 \(n\) 个区间 \([l_i,r_i]\),令 \(S=s[l_1,r_2]+s[l_2,r_2]+\dots +s[l_n,r_n]\)\(q\) 次询问一个 \(01\) 串在 \(S\) 中的出现次数。\(n,q\le 5\times 10^5,1\le l_i\le r_i\le 10^9\)

这种形式的查询多个串在某个串中的出现次数太典了,不过是字符串形式还是 \(01\) 串或者是满足某种要求的结构,都要反应过来是建立 AC 自动机。

对于询问串建立 AC 自动机,然后我们需要用文本串在 AC 自动机上行走,对于祖先链产生贡献,可是无法显式遍历,通过一些手段加速,这里要利用二进制的性质。

考虑一个倍增结构,\(s[0,2^{k}-1]=s[0,2^{k-1}-1]+s'[0,2^{k-1}-1]\),其中 \(s'\)\(s\)\(01\) 翻转。

下文为了方便,记 \(s_0=s,s_1=s'\)

进一步推广,\(s[k2^n,(k+1)2^n-1]\) 根据 \({\rm popcount(k)}\bmod 2=z\) 等价于 \(s_z[0,2^n-1]\)

因此我们可以任意分解 \(s[l,r]\) 为 $\log $ 个整段拼接了,分解方法:从小到大枚举 \(n\),再从大往小枚举,一旦遇到 \(2^n \mid l\)\(l+2^n-1\le r\),就找到 \(k=\dfrac{l}{2^n}\),然后使用 \(s_{{\rm popcount(k)}\bmod 2}[0,2^n-1]\) 来更新,再 \(l\gets l+2^n\)

在 AC 自动机上,设 \(f_{u,n,0/1}\) 表示从 ACAM 上出发,走 \(s_{0/1}[0,2^{n}-1]\) 会走到的点,可以通过简单递推求出:\(f_{u,n,0/1}=f_{f_{u,n-1,0/1},n-1,1/0}\)。所以对于 \(s[l,r]\) 可以通过 \(f\) 在树上快速遍历。

同时只做到遍历还是不够,我们需要给经过的节点计数器 \(+1\),最后再子树求和得到答案。

于是设

题解

某题(无来源)

称两个序列本质相同,当且仅当序列内部不含相同数,且内部相对大小关系相同。
给定一个 \(1\sim n\) 的排列 \(p\)\(q\) 次查询,每次查询一个 \(1\sim m_i\) 的排列,问有多少个 \(p\) 的区间和该小排列本质相同。\(n,m_i,\sum len_i \le 2\times 10^5\)

考虑如下映射:\(\{a_1,a_2...a_n\} \to \{a_1',a_2'..a_n'\}\),其中 \(a_i'=\sum\limits_{j=1}^{i-1}[a_j>a_i]\)

我们对于询问串建立 AC 自动机,更改 \(fail\) 定义(原本是找到一个最长的后缀 \(t\) 也在后缀自动机上),原本跳完 \(fail\) 后是 \(c\) 相等,但是区间改变了,所以我们要看有无一个 \(c'\) ,使得跳上去对应的子区间内 \(c'\)\(u\) 往前一个距离的得到的 \(c\) 相同。

然后跑 \(P\) 的时候,也是根据上述更改后的 \(fail\) 定义来跳。

CF1801G A task for substrings

我们可以用类似前缀和来算,\([1,r]-[1,l-1]\)。但是这样会多算那些跨过 \(l\) 的串。于是就要求有多少 \((A,B)\),满足 \(A+B \in \{s_1,s_2...s_n\}\)。且 \(A\)\(s[1,l-1]\) 的后缀,\(B\)\(s[l,r]\) 的前缀

任意字符集大小建立AC自动机

考虑处理 \(u\) 点的所有出边,如果存在 \(c\) 边,就是 \(ch_{u,c}\),否则 \(ch_{u,c}=ch_{fail_u,c}\),这是一个继承的形式。

我们可以建立一个可持久化线段树直接继承 \(fail_u\) 的信息,然后每次出现了新边就单点改一下。

QOJ7742. Suffix Structure

我们先对于 Trie 建立出 AC 自动机,然后发现每个 \(d_x\) 就是从 AC 自动机上的 \(s_i\) 节点开始跑串 \(t\),然后跳 \(fail\) 到哪个节点就是终点了。

QOJ5312. Levenshtein Distance

改题:

对于两个字符串 \(s\)\(t\) 和常数 \(k\)。求 \(\min(k,s 与 t 编辑距离)\)\(\lvert S\rvert,\lvert T\rvert \le 10^5,k\le 5000\)

\(dp_{i,j}\) 表示 \(s[1,i]\) 匹配 \(t[1-j]\) 的最小编辑距离。

于是 \(dp_{i,j}=\min(dp_{i-1,j-1}+[s_i\neq t_j],dp_{i-1,j}+1,dp_{i,j-1}+1)\)

我们只要考虑所有 \(\lvert i-j\rvert \le k\) 的位置即可,复杂度 \(O(nk)\)

还有一个性质就是 \(dp_{i-1,j-1}\le dp_{i,j}\)

\(f_{v,t}\) 表示 \(\lvert i-j\rvert=v\) 的时候,编辑距离用了 \(t\) 的时候的最大 \(i\),然后每次转移用 SA 匹配一下找到

前缀本质不同子串数

P4770 [NOI2018] 你的名字

先思考特殊性质询问 \(S[1:n]\) 的情况,建立 \(S\) 串的 SAM。我们对于 \(T\)\(r\),求最小的 \(l\) 使得 \(T[l,r] \in S\)。假设目前在 \(T[l,r]\) 的节点上,我们不断跳 link ( \(l\) 不断变大),使得存在一个 \(T_{r+1}\) 的出边。这样子可以在 \(O(\lvert T\rvert)\) 的时间内求解。然后我们对于 \(T\) 再建立 SAM,

查一个子串是否在 \(s[l,r]\) 内出现过,可以线段树合并,求 \(l\) 之后的第一次出现的那个串。

P4482 [BJWC2018] Border 的四种求法

P6292 区间本质不同子串个数

可以用扫描线,扫到 \(R\),用一个数据结构维护所有 \(L\) 的答案。移动 \(R\),发现只会多了右端点是 \(R\) 的串。

而正好 endpos 集合对应的就是右端点集合相同的串,就可以放在一起处理了。除此之外,当 endpos 集合不同的时候,也有可能本次和上次右端点都相同。于是我们在 SAM 上记录一下 \(lst_u\) 表示 \(u\) 节点上一次出现的右端点。

  1. \(s[l,r]\) 到根的链划分为若干段 \(lst\) 相等。
  2. \(s[1,r]\) 到根的链设为 \(r\)

我们发现这个操作类似于 lct,时间复杂度 \(O(n\log^2n+q\log n)\)

P9717 [EC Final 2022] Binary String

打表观察一下,发现这其实是一个 \(1\) 分散的过程,最后生成的 \(1\) 尽可能分散。

同时发现,对于一个长度大于 \(1\)\(0\) 段,如果左边不与 \(1\) 段相邻而是单个 \(1\) 相邻,那么每次操作后会整体左移。

对于一个长度大于 \(1\)\(1\) 段,如果左边

QOJ6507. Recover the String

对于串 \(s[l,r]\) 一个点最多有两个度,\(s[l+1,r]\)\(s[l,r-1]\),一个点 \(u\) 的入点集合为 \(G(u)\),它表示的字符串为 \(S(u)\)

如果度数为 \(1\),就代表上述两个串相等,那么 \(s[l,r]\) 就是由单个字符构成。

如果度数为 \(2\) 的时候,

AGC018F Two Trees

区间线性基

区间询问线性基是否包含 \(x\)。每次遇到冲突时候,让下标最大的留下,下标下的继续往后走。

QOJ7905. Ticket to Ride

\(dp_{i,j}\) 表示 \(1-i\) 里面选了 \(j\) 个获得的最大收益。

\[dp_{i,j}=\max\{dp_{i-1,j},dp_{i-k,j-k}+v(i-k+1,i)\} \]

暴力做时间复杂度 \(O(n^3)\)。观察到对于第二个转移每个两个变量减小的是一样的,于是 令 \(t=i-j\)\(dp_{t,j}=\max\{dp_{t-1,j},dp_{t,j-k}+val(i-k+1,i)\}\)

第一个转移直接继承 \(i-1\) 就行了,第二个转移固定 \(i\) 可以看成 \(dp_j=\max dp_{j-k}+val(i-k+1,i)\)

\(z=j-k\)\(dp_j=\max dp_z+val(i+j-z+1,i)\)

就是一个前缀加,全局 \(\max\),末尾加元素的数据结构。

可以用线段树维护,也可以用并查集更快。

对于 \(i<j\),如果有 \(a_i>a_j\),那么 \(j\) 无用。所以我们只需要维护一个单调递增的序列,将 \([i,nxt_i)\) 合在一起,其中 \(nxt_i\) 表示 \(i\) 后面第一个满足 \(a_i<a_j\)\(j\)。每次前缀加,找到最后一个加的位置所属的块,整体抬升与后面比较。

P8173 [CEOI2021] Newspapers

考虑用可能存在集合来做。
初始查询 \(n-1\)\(\{\}\)

P9731 [CEOI2023] Balance

先考虑 \(S=2\) 的做法,我们对于
对于 \(S\) 更大的做法,我们先做 \(S=2\) 然后分治下去。

某题

给定平面上 \(n\) 个点,保证没有三点共线,四点共圆。
询问有多少本质不同的圆。
两个圆本质相同,当且仅当包含的点集相同。
\(n\le 500\)

答案是 \(\begin{pmatrix}n\\1\end{pmatrix}+\begin{pmatrix}n\\2\end{pmatrix}+\begin{pmatrix}n\\3\end{pmatrix}+1\)\(1\) 是一个空的圆,第一项是只包含一个点的圆。

posted @ 2024-08-01 10:37  Mirasycle  阅读(81)  评论(0)    收藏  举报