后缀自动机(个人笔记)
柒葉灬
2019-04-18 20:44:21
# 后缀自动机
------
具体原理就不多叙述了,网上已经很清楚了。
-----------
#### 模板
```cpp
struct node{
int son[26],len,fa;
void clear(){
memset(son,0,sizeof(son));
len=fa=0;
}
}t[maxn<<1];
void extend(int c){
int p=last,np=last=++tot;
t[np].clear();
t[np].len=t[p].len+1;
while(p&&!t[p].son[c])t[p].son[c]=np,p=t[p].fa;
if(!p)t[np].fa=1;
else {
int q=t[p].son[c];
if(t[q].len==t[p].len+1)t[np].fa=q;
else {
int nq=++tot;
t[nq]=t[q];t[nq].len=t[p].len+1;
t[q].fa=t[np].fa=nq;
while(p&&t[p].son[c]==q)t[p].son[c]=nq,p=t[p].fa;
}
}
}
```
------
#### 一句话题解系列(雾):
[专题A](https://cn.vjudge.net/contest/287949#problem/A)
每一次询问的时候重新在$[l,r]$区间内建$SAM$
看似很暴力,实则复杂度是对的,
**记住SAM建立的复杂度是$O(n)$的。**
-------------
[专题B](https://cn.vjudge.net/contest/287949#problem/B)
题意:有多少个是字符串A的子串,但不是字符串Bi的子串。
输入Bi串的时候跑一遍自动机,标记每个点可延伸最长的子串
合法串的个数就是$max(0,t[x].len-max(Max[x],t[f].len))$
拓扑排序把最大值贡献给父亲,
---------
[专题C](https://cn.vjudge.net/contest/287949#problem/C)
**题意:查找两个串的最长公共子串**
对着一个建SAM,然后另一个串跑自动机,
如果$trans$指向的点为0时,跳slink数组,
然后更新当前匹配长度,$len=t[x].len$
可以发现,这跳slink的操作就像跳fail数组一样,
答案就是len的最大值。
(不知道为什么强行把$last=1$会对)
----------
[专题D](https://cn.vjudge.net/contest/287949#problem/D)
题意:给n个串,输出询问串有多少个与其有相同endpos的串
每两个串之间插入一个从未出现过的字符,
求出每个节点的最小len值,防止有特殊字符串被计入答案。
------
[专题E](https://cn.vjudge.net/contest/287949#problem/E)
题意:求有多少个子串出现至少两次,且两次不重叠。
算每一个节点endpos的最大值和最小值,
计算每一个节点贡献子串就行了。
[专题F](https://cn.vjudge.net/contest/287949#problem/F)
题意:求有多少个子串出现恰好$K$次。(水题)
每个节点出现个数就是endpos的个数,
拓扑排序收集个数即可
[专题G](https://cn.vjudge.net/contest/287949#problem/G)
**题意:有多少个子串出现至少$K$次,有加字符操作**
离线,建一个完整的SAM,
算每一个节点第K个endpos的位置,
需要用到**线段树合并**
每一次计算之后合并到父亲即可,不需要可持久化。
-------
[专题H](https://cn.vjudge.net/contest/287949#problem/H)
_~~题意:详见后缀数组专题的翻译(233)~~_
对串反向建SAM,维护每一个节点最小的endpos
记录下每一个后缀的对应的节点,
倍增跳fa,找到第一个mn小于当前x的节点,len就是最大lcp
----------
[专题I](https://cn.vjudge.net/contest/287949#problem/I)
### (难题)
**题意:从A中取一个子串x,B中取一个子串y,问x+y可以形成多少不同的新串(x,y均可以为空串)**
这题最麻烦的就是会有重复的,即使$x!=x',y!=y'$,也有可能出现$x+y==x'+y'$
为了防止这种情况出现,可以使取出的x尽可能长,
即,x加上y的第一个字符**不属于A的子串**
最终得到:扫一遍A的SAM,若trans(x,i)==NULL,
则判断B当中以i为开始有多少不同子串即可,
最后在特殊处理一下空串的情况就能A了。
-----------
[专题J](https://cn.vjudge.net/contest/287949#problem/J)
### (好题)
**题意:给一个目标串。每次有两种操作:**
**1.在当前串后面加字符,代价为对应字符的成本cost[i]**
**2.选则一段长度len的子串,复制到后面,花费A\*len+2*B**
**初始为空串,问达到目标串的最小代价。**
这题显然是需要dp的,每次转移时重点就在操作2上,
可以发现,只要知道最远能从哪里转移就行了,
区间求min,**用树状数组维护最小值,倍增维护最远点。**
### 补充:
这题可以在线建立着一个SAM,
使得这个SAM的长度就是上面提到的需要维护的最小值$j$,
如果不能匹配了就$extend(str[++j])$
直到能够匹配或者$i==j$
复杂度从$O(nlogn)$到$O(n)$
-----------------