后缀自动机能识别字符串S的所有子串,是一个DAG。
http://blog.csdn.net/huanghongxun/article/details/51112764
https://blog.xehoth.cc/SuffixAutomation/#一些性质
hihocoder上的一堆SAM入门教程挺友好的。
附SAM模板
1 const int N = 1e6+10; 2 struct SAM{ 3 //pre[]: parent树, pre[i]是i的后缀, 字符串长度小于i 4 //son[][]: trans DAG图 5 //ml[]: maxlen, min[i] = max[pre[i]]+1, 不同的字符串个数是maxlen-minlen+1 6 int tot, last, pre[N<<1], son[N<<1][26], ml[N<<1]; 7 void init() { 8 tot = 1, last = 1; 9 memset(son[1], 0, sizeof son[1]); 10 } 11 void extend(char c){ 12 int w = c-'a', p = ++tot, x = last, r, q; 13 memset(son[p], 0, sizeof son[p]); 14 for(ml[last = p] = ml[x]+1; x&&!son[x][w]; x = pre[x]) son[x][w] = p; 15 if(!x) pre[p] = 1; 16 else if(ml[x]+1 == ml[q = son[x][w]]) pre[p] = q; 17 else{ 18 pre[r = ++tot] = pre[q]; 19 memcpy(son[r], son[q], sizeof son[r]); 20 ml[r] = ml[x]+1; 21 pre[p] = pre[q] = r; 22 for(; x&&son[x][w] == q; x = pre[x]) son[x][w] = r; 23 } 24 } 25 };
结点:
后缀自动机的节点表示一类不同的子串,它们在原串中出现的位置的Ri全部相同。(Right集合相同)
节点的属性就是1. Right集 2.长度区间[min(s), max(s)] (表示该节点表示的子串的长度范围)。
边:
边分两类,转移边与parent边。
转移边就是读入下一个字符后跳转的结点。故转移过去的节点对应的字符串集合至少包含原节点的字符串集添加字符。
parent边就是fail边,每次经fail边跳转后,max(fa(s))=min(s)−1,一个节点及其父节点的代表的串有相同的后缀
沿trans图前行,节点对应的字符串集合变大;
沿parent树回溯,节点对应字符串长度区间[minlen, maxlen] -> [?, minlen-1],right集合变大
节点对应的不同子串数 = maxlen-minlen+1 (所有不同子串数 = 各节点求和 = 从root出发的可行路径条数(dp) )
节点对应的字符串在原串中出现的次数 = 节点对应的right集合大小 = trans图中节点走到终点态的方案数 = parent树中子树在主链上的节点数
循环同构字符串处理技巧: 构造s0...sn-1s0...sn-2
如何求s, t的最长公共子串?
构造出s的SAM,用t在SAM上跑,维护当前匹配的最长长度len,
读入一个字符,
若沿trans图有对应边,则len = len+1;
否则沿parent树回溯,则len = maxlen
然后要理解后缀自动机的节点数不超过2n−1(n≥3), 转移数不超过3n−3条。
(转移数想的时间比较久。首先因为只有n个后缀,故出度为0的点不超过n个。那么假设只有2n-2条边,构成一棵生成树,那么每再加入一条边a -> b,我们都能有一条root -> a - > b -> end的路径,表示某一后缀。
root -> a, b -> end都是生成树上的边。那么加入的边不会超过n个就能构造出所有后缀。经过从具体请戳第一个链接)
=======================================================================================
应用:http://blog.csdn.net/huanghongxun/article/details/51112764