Loading

字符串

周期与 Border 的结构

我们定义正整数 \(p\) 是串 \(S\) 的周期,当且仅当 \(p\le |S|\)\(\forall i\in [1,|S|-p],S_i=S_{i+p}\)

我们定义串 \(T\) 是串 \(S\) 的 border,当且仅当 \(T\)\(S\) 的前缀且 \(T\)\(S\) 的后缀,且 \(T\neq S\)

周期与 border 的关系

正整数 \(p\) 是串 \(S\) 的周期,当且仅当 \(p\le |S|\)\(S_{1\dots (|S|-p)}\)\(S\) 的 border。

弱周期引理

\(p,q\) 是串 \(S\) 的周期,且 \(p+q\le |S|\),那么 \(\gcd(p,q)\) 也是 \(S\) 的周期。

不妨设 \(p<q\)

对于任意 \(q<i\le |S|\),有 \(S_i=S_{i-q}=S_{i-q+p}\)。对于任意 \(q-p<i\le q\),有 \(S_i=S_{i+p}=S_{i-q+p}\)。所以 \(q-p\) 也是 \(S\) 的周期,而 \((q-p)+p\le |S|\),归纳可知 \(\gcd(p,q)\)\(S\) 的周期。

周期引理

\(p,q\) 是串 \(S\) 的周期,且 \(p+q-\gcd(p,q)\le |S|\),那么 \(\gcd(p,q)\) 也是 \(S\) 的周期。

证明略。

短周期结构

\(S\) 的所有长度不超过 \(\frac{|S|}{2}\) 的周期,都是其最短周期的倍数。

这是弱周期引理的直接推论。

Border 的等差数列结构

\(S\) 的所有 border(或周期)的长度形成了 \(O(\log |S|)\) 个值域不交的等差数列。

首先有一个引理:若 \(S_{1\dots q}\)\(S\) 的 border,\(S_{1\dots p}\)\(S_{1\dots q}\) 的 border,则 \(S_{1\dots p}\) 也是 \(S\) 的 border。

考虑 \(S\) 的所有长度 \(\ge \frac{|S|}{2}\) 的 border,根据“短周期结构”,它们形成一个等差数列。设长度 \(<\frac{|S|}{2}\) 的 border 中,最长的一个的长度为 \(l\),那么长度在 \([\frac{l}{2},l]\) 中的所有 border 构成一个等差数列。以此类推,总共会形成 \(O(\log |S|)\) 个等差数列。

P3426 [POI2005] SZA-Template

给定一个字符串 \(S\),找到一个长度最小的字符串 \(T\) 使得 \(T\)\(S\) 中的所有匹配区间覆盖了整个 \([1,|S|]\cap \mathbb{Z}\)

首先,\(T\) 一定是 \(S\) 的 border。从短到长考虑 \(S\) 的所有 border \(W\),并维护 \(W\)\(S\) 中的所有匹配位置 \(p_1<p_2<\dots<p_k\)。假如 \(\forall 1\le i<k,p_{i+1}-p_i\le |W|\),那么 \(W\) 就是答案。

\(S_{i\dots (i+|W|-1)}=W\) 当且仅当 \(\mathrm{border}_{i+|W|-1}\ge |W|\)。所以匹配位置就是 \(\mathrm{border}\ge |W|\) 的位置,用链表维护即可。时间复杂度 \(O(n)\)

P4156 [WC2016] 论战捆竹竿

给定一个长度为 \(n\) 的字符串 \(S\) 和一个正整数 \(w\)(此处的 \(w\) 相较于题面中的减去了 \(n\)),设 \(S\) 的所有周期为 \(a_1,a_2,\dots,a_k\),求有多少个整数 \(x\in [1,w]\) 能被表示成 \(b_1a_1+b_2a_2+\dots+b_ka_k\) 的形式,其中 \(b_i\ge 0\)

\(1\le n\le 5\times 10^5,1\le w\le 10^{18}\)

首先,\(S\) 的所有周期可以被分为 \(O(\log n)\) 个值域不交的等差数列。考虑同余最短路,选定首项最小的等差数列的首项作为模数 \(m\),对于一个首项为 \(p\),公差为 \(d\),项数为 \(l\) 的等差数列,对于所有 \(x\in [0,m)\)\(y\in [0,l)\),连边 \(x\xrightarrow{p+dy} ((x+p+dy)\bmod m)\)。最终,设 \(0\to x\) 的最短路是 \(dis_x\),答案就是 \(\sum_{0\le i<m} \max(\lfloor (w-dis_i)/m\rfloor+1,0)\)

上面的做法时间复杂度显然过高。先考虑一个等差数列 \((p,d,l)\) 怎么做。选定 \(m=p\),连边就被简化成了:对于所有 \(x\in [0,m)\)\(y\in [0,l)\),连边 \(x\xrightarrow{p+dy} ((x+dy)\bmod m)\)。可以发现这些 \(x\) 能被分成 \(\gcd(p,d)\) 个连通块,连通块内的连边结构很简单,可以用单调队列维护。

再考虑,假如当前我们得到了前 \(i\) 个等差数列的答案 \(dis_x\),现在要将模数 \(m\)\(p\) 更改成 \(p'\)\(p'>p\))。原来所有能被表示出来的数字 \(z\) 满足:

\[\exists y\in [0,p), \begin{cases} z\equiv y\pmod{p}\\ z\ge dis_y \end{cases} \]

在满足这两个条件的情况下,可以令 \(dis'_{z\bmod p'}\gets \min(dis'_{z\bmod p'},z)\),而初值是 \(dis'_{dis_x \bmod p'}=dis_x\)

我们仍然将图拆成若干个环,然后对于每个环分别更新一遍即可。总时间复杂度 \(O(n\log n)\)代码

CF1286E Fedya the Potter Strikes Back

给定长度为 \(n\) 的字符串 \(S\) 和非负整数数列 \(\{W_n\}\),对于 \(1\le l\le r\le n\),定义

\[f(l,r)=\begin{cases} \min_{l\le i\le r} \{W_i\} & S_{l\dots r}=S_{1\dots (r-l+1)}\\ 0 & \text{otherwise} \end{cases}. \]

对于每个 \(1\le i\le n\),求 \(\sum_{1\le l\le r\le i} f(l,r)\) 的值,强制在线。
\(1\le n\le 6\times 10^5,0\le W_i<2^{30}\)

假如我们已经知道了所有 \(1\le l\le r\le i-1\)\(f(l,r)\) 之和,要计算出 \(r=i\) 时的 \(\sum_{l} f(l,r)\)。对于每个 \(j\),设 \(B_j\) 表示 \(S_{1\dots j}\) 的 border 的长度集合。考虑如何从 \(B_{i-1}\) 推出 \(B_i\),对于每个 \(x\in B_{i-1}\),若 \(S_{x+1}=S_i\),则 \(B_i\) 中会包含 \(x+1\)。还有长度为 \(1\) 的 border,若 \(S_1=S_i\),则 \(B_i\) 中会包含 \(1\)

std::map 维护每种 \(f\) 对应的 border 个数。我们需要支持:

  • 将所有 border 的权值与 \(W_i\)\(\min\):暴力做即可,复杂度均摊 \(O(n\log n)\),因为合并 \(x\) 个权值不同的 border 集合的代价是 \(\Theta(x)\),而合并后一定不会再分裂开。
  • 删除一个 border:在 \(W_{1\dots (i-1)}\) 对应的单调栈中二分,算出这个 border 的 \(f\),然后直接删除即可。

总时间复杂度 \(O(n\log n)\),常数略大。代码

P5287 [HNOI2019] JOJO

这道题可以分为两部分:支持添加多个相同字符的 KMP,以及可撤销 KMP。

添加多个相同字符

将 (长度,字符) 二元组看作字符集,设原来的字符串是 \(S=((x_1,c_1),(x_2,c_2),\dots,(x_{n-1},c_{n-1}))\),当前要添加一个 \((x_n,c_n)\)。考虑一个能延伸到 \((x_n,c_n)\) 的 border,设它是 \(((x'_1,c'_1),(x'_2,c'_2),\dots,(x'_k,c'_k))\),那么它必须满足:

  • \(c_1=c'_1\land x'_1\le x_1\)
  • \(\forall i\in [2,k-1],c_i=c'_i\land x_i=x'_i\)
  • \(c_k=c'_k\)

也就是说,除开头和结尾以外的位置,不仅字符要匹配,个数也要匹配。因此,我们维护在新字符集上的 fail 指针,在往前跳的过程中更新答案即可。

可撤销 KMP

有很多种写法都是错的,这里只给一个正确写法(感谢 142857cs 和 jiangly 的帮助):

for (int i = 2, j = 0;i <= n;++i) {
	for (int pre = 0;j && s[j + 1] != s[i];) {
		if (j - kmp[j] == pre) j = j % pre + pre;
		pre = j - kmp[j], j = kmp[j];
	}
	if (s[j + 1] == s[i]) ++j;
	kmp[i] = j;
}

将这两个东西合起来即可通过本题,时间复杂度 \(O(n\log n)\)代码

后缀自动机

定义与性质

对于一个字符串 \(S\),设其字符集为 \(\Sigma\),则 \(S\) 的 SAM 就是一个最小的接受所有 \(S\) 的后缀的自动机。设 SAM 的源点 \(t_0=0\),那么从 \(t_0\) 开始的路径与 \(S\) 的子串一一对应。

对于 \(S\) 的非空子串 \(T\),设 \(\operatorname{endpos}(T)\)\(T\)\(S\) 中的所有出现位置的右端点的集合。若干个不同的 \(T\)\(\operatorname{endpos}\) 可能是相同的,我们根据 \(\operatorname{endpos}\)\(S\) 的所有子串分成若干个 \(\operatorname{endpos}\) 等价类,SAM 中的一个不是 \(t_0\) 的节点就代表了一个 \(\operatorname{endpos}\) 等价类,而 \(t_0\) 代表的是 \(\varnothing\)

性质 \(1\):若 \(T,W(|W|<|T|)\)\(S\) 的两个非空子串,那么 \(\operatorname{endpos}(T)=\operatorname{endpos}(W)\) 当且仅当 \(W\)\(S\) 中的所有出现都是以 \(T\) 的后缀的形式出现的。

性质 \(2\):若 \(T,W(|W|<|T|)\)\(S\) 的两个非空子串,那么它们 \(\operatorname{endpos}\) 的关系只有两种:

\[\begin{cases} \operatorname{endpos}(T)\subseteq \operatorname{endpos}(W) & W \text{is a suffix of } T\\ \operatorname{endpos}(T)\cap \operatorname{endpos}(W)=\varnothing & \text{otherwise} \end{cases} \]

性质 \(3\):对于一个 \(\operatorname{endpos}\) 等价类,将其中的字符串按照长度从小到大排列后,对于任意两个相邻的字符串 \(T,W\),有:

  • \(T\)\(W\) 的后缀;
  • \(|T|=|W|-1\)

对于 SAM 上的一个节点 \(i\),我们定义 \(\operatorname{len}(i)\)\(i\) 代表的等价类中字符串的最长长度,而 \(\operatorname{minlen}(i)\) 为最短长度。定义后缀链接 \(\operatorname{link}(i)\) 为一个节点 \(j\) 使得 \(\operatorname{len}(j)\) 最大且 \(j\) 代表的等价类的 \(\operatorname{endpos}\)\(i\) 代表的等价类的 \(\operatorname{endpos}\) 的超集,特别地 \(\operatorname{link}(t_0)=-1\)。据此,我们可以得出 \(\operatorname{len}(\operatorname{link}(u))=\operatorname{minlen}(u)-1\)

性质 \(4\):对于所有 \(i>0\),将 \(i\to \operatorname{link}(i)\) 间连边,则会形成一棵以 \(t_0\) 为根的内向树,这棵树描述的包含、不交关系与 \(\operatorname{endpos}\) 描述的相同。

我们考虑 SAM 中转移边的意义。设 \(\delta(u,c)\) 为点 \(u\) 添加字符 \(c\) 得到的状态,\(\operatorname{substr}(u)\) 为点 \(u\) 表示的所有字符串的集合。若两个点 \(u,v\) 之间存在转移边 \(u\xrightarrow{c} v\),则 \(\forall T\in \operatorname{substr}(u),T+c\in \operatorname{substr}(v)\)

性质 \(5\):对于节点 \(v\),设 \(\operatorname{mintrans}(v),\operatorname{maxtrans}(v)\) 分别是满足 \(\operatorname{len}(u)+1=\operatorname{minlen}(v),\operatorname{len}(u)+1=\operatorname{len}(v)\) 且存在转移边 \(u\to v\)\(u\),易证这样的 \(u\) 存在且唯一。那么对于所有存在转移边 \(u\to v\)\(u\),它们在 link 树上形成了一条 \(\operatorname{maxtrans}(v)\leadsto \operatorname{mintrans}(v)\) 的直上直下的路径。

构建

SAM 的构建是增量构造的过程,且是在线的。设我们已完成了 \(S_{1\dots (i-1)}\) 的构建,现在要加入 \(S_i\) 这个字符,\(S_{1\dots (i-1)}\) 对应的状态是 \(last\)

新建一个节点 \(cur\),设 \(p=last\),我们不断地让 \(p\gets \operatorname{link}(p)\),并令 \(\delta(p,S_i)=cur\),直到 \(p=-1\) 或存在 \(\delta(p,S_i)\)。分类讨论:

  • \(p=-1\):令 \(\operatorname{link}(cur)\gets t_0\)
  • 否则,设 \(\delta(p,S_i)=q\),若 \(\operatorname{len}(p)+1=\operatorname{len}(q)\),直接令 \(\operatorname{link}(cur)\gets q\) 即可。
  • 否则,\(\operatorname{len}(p)+1<\operatorname{len}(q)\)。我们将 \(q\) 拆成两个状态 \(cl,q\),并令 \(\operatorname{len}(cl)=\operatorname{len}(p)+1\)。对于任意字符 \(c\),都令 \(\delta(cl,c)\gets \delta(q,c)\)。令 \(\operatorname{link}(cl)\gets \operatorname{link}(q),\operatorname{len}(q)\gets\operatorname{len}(cl)+1,\operatorname{link}(q)\gets cl,\operatorname{link}(cur)\gets cl\),并将所有位于 \(p\leadsto t_0\) 的路径上的点 \(p'\)\(\delta(p',S_i)\gets cl\)

不会复杂度证明。

struct SAM{
	struct Node{int len,link,ed;array<int,26> nxt;}st[N*2];
	int size,last,pres[N];
	int New(){
		st[size].len=st[size].link=st[size].ed=0;
		st[size].nxt.fill(-1);
		return size++;
	}
	SAM():size(0),last(0){New();st[0].link=-1;}
	void Extend(char _c){
		int cur=New(),c=_c-'a';
		st[cur].len=st[last].len+1,st[cur].ed=1;
		int p=last;pres[st[cur].len]=last=cur;
		while(~p&&!~st[p].nxt[c]){
			st[p].nxt[c]=cur,p=st[p].link;
		}
		if(!~p){st[cur].link=0;return;}
		int q=st[p].nxt[c];
		if(st[p].len+1==st[q].len) st[cur].link=q;
		else{
			int cl=size++;
			st[cl]={st[p].len+1,st[q].link,0,st[q].nxt};
			while(~p&&st[p].nxt[c]==q){
				st[p].nxt[c]=cl,p=st[p].link;
			}
			st[q].link=st[cur].link=cl;
		}
	}
	int anc[N*2][LogN];
	void Build(){
		For(i,1,size-1) anc[i][0]=st[i].link;
		For(j,1,LogN-1) For(i,1,size-1) anc[i][j]=anc[anc[i][j-1]][j-1];
	}
	int Substr(int l,int r){
		int len=r-l+1,u=pres[st[last].len-l+1];
		Dec(j,LogN-1,0) if(st[anc[u][j]].len>=len) u=anc[u][j];
		return u;
	}
}sam;

回文结构

Manacher 算法

对于字符串 \(S\),设 \(p_i\) 为以 \(i\) 为回文中心的最长延伸长度,也就是说 \(S_{(i-p_i+1)\dots (i+p_i-1)}\) 是极长的回文串。Manacher 可以在 \(O(|S|)\) 时间内求出所有 \(p_i\)

构造新的字符串 \(T\) 使得 \(T_{2i}=S_i,T_{2i-1}=\texttt{/},T_{2n+1}=\texttt{/}\),这样我们可以同时处理回文中心落在某个字符上、以及落在空隙中的情况。从小到大枚举 \(i\) 并维护当前向右延伸得最远的回文子串 \(S_{d\dots r}\)。考虑利用 \(d,r\) 加速 \(i\) 这个位置的回文串的枚举:若 \(i\le r\) 则令 \(p_i\gets \min(r-i+1,p_{2d-i})\)。随后,不断增大 \(p_i\) 直到 \(S_{i-p_i}\neq S_{i+p_i}\)

时间复杂度:每次 \(p_i\) 增加 \(1\) 都会使 \(r\) 增加 \(1\),因此时间复杂度为 \(O(|S|)\)

P5446 [THUPC2018] 绿绿和串串

参考资料

posted @ 2022-08-29 19:26  Alan_Zhao_2007  阅读(92)  评论(1编辑  收藏  举报