bzoj3926: [Zjoi2015]诸神眷顾的幻想乡 对[广义后缀自动机]的一些理解
先说一下对后缀自动机的理解,主要是对构造过程的理解。
构造中,我们已经得到了前L个字符的后缀自动机,现在我们要得到L+1个字符的后缀自动机,什么需要改变呢?
首先,子串$[0,L+1)$对应的状态不存在,应当建立一个状态来表示这个串,显然,这个状态(np)的right集合是{L+1},max=L+1。
现在新建立了一个状态,我们还有两件事要干:找出能转移到这个状态的状态,建立链接;确定这个状态的min,即找到它在parent树上的父亲。
能转移到np的状态显然都是right集合包含L的状态,即p(子串$[0,L)$所在的状态)及p的祖先。
设c = s[L+1]我们沿着p往上爬,会遇到一些没有c的转移的状态,显然此时直接将c的转移连向它即可。
如果全都没有c的转移,那么np的父亲设为root,也就是说找到了np的min,为1。
否则,现在我们到了第一个含有c的转移的状态,此时p代表红色部分的状态。
如上图,至少存在两个红色的部分,他们是相同的,且其中一个位置为L,另一个位置的下一个字符是c,(注意,红色线段以及蓝色线段的右端代表它的位置,左端代表位置减去max的地方)。设q=p->to[c],此时我们已经可以确定np的min了,就是p->max+2,即np的父亲的max应该为p->max+1。
这样,如果是图中的绿色状态,即q->max == p->max+1我们就可以宣布,找到了p的父亲。然后令p->par = q,这样在q以及所有的祖先的right集合中插入了L+1这个位置。
如果是蓝色状态,我们不得不考虑创建一个新的节点nq使它的max为p->max+1,来作为np的父亲,所以我们令nq->max = p->max + 1, np->par = nq,这样之前提到的两个问题已经全部解决了,但是事实上我们需要在p->to[c]这个状态的right集合中插入L+1,所以我们令nq->par = q->par, q->par = nq,则nq这个状态表示right集合正是现在的p->to[c]需要的,接下来我们把p以及所有p的祖先中到q的转移全部换成nq,这也要求nq和q具有相同的转移,需要memcpy(nq->to, q->to, sizeof(nq->to));
然后再谈一下广义后缀自动机,它可以同时接受若干个串,不妨考虑一棵trie,因为所有的串都可以插在trie上。特别地,这棵trie中的节点还可能拥有相同的儿子,这样可以方便我们在线插入。
与单个字符串的后缀自动机略有区别的是,此时的right集合实际上是trie上的节点,而不是一个长度L,我们考虑按照dfs序建成了如下trie树的后缀自动机,五角星部分是正在加入的。跟之前一样,只有p以及p的祖先是我们需要考虑的。
这个位置要插入的串是红色部分以及它所有的后缀,跟单个串的后缀自动机不同的是,此时p可能已经有了c的转移。按照之前说的,我们有两个事情,一个是创建一个新的状态来表示红色部分的字符串,这个状态的max应该是p->max+1,并且right集合应包含五角星所在的这个节点,第二个就是找到这个新的状态的min也就是它在parent树上的父亲。不同的是,之前我们一定会新建一个节点,因为p能转移到的状态的max是不可能达到p->max+1的,但是现在有可能已经存在这样的状态,我们可以直接在这个状态的right集合中插入五角星所在的节点,并且它的父亲之前已经找好了,我们可以直接结束。如果图中的只有绿色部分和蓝色部分与红色部分相同,且他们上面的字符有一个不同,就意味着这种状态存在。
如果只有蓝色部分跟红色部分相同,那么蓝色状态的max不会是p->max+1,肯定是 > p->max + 1的,此时需要新建一个节点,而我们已经找到了这个新节点的父亲,就是p->to[c]。
这样就对应一段新的代码:
if(p->to[c]) { Node *q = p->to[c]; if(q->maxl == p->maxl + 1) return q; /*q->right++*/ Node *np = new(pis++) Node(*q); np->maxl = p->maxl + 1, q->par = np; for(; p && p->to[c] == q; p = p->par) p->to[c] = np; return np; }
当然,我们可以忽视不用创建节点的情况,每次都创建一个节点,后果就是有可能它的max跟它的父亲相同,这样没有状态能转移到它,它也不表示任何一个子串,它存在的意义只是为它的祖先贡献了的right集合一个位置,当然它在parent树中还必须起一个连接作用。代码可以完全不改变。
而按照最简状态后缀自动机的定义,这种状态是不应该存在的,但是存在的话问题也不大。
另外,在一棵严格的trie树(没有相同的儿子)上按照深度建立后缀自动机是不用考虑上面的情况的,因为这种情况一定不会出现。
下面是bzoj3926 [ZJOI2015]诸神眷顾的幻想乡 的代码,作为参考。
1 #include<bits/stdc++.h> 2 3 typedef long long LL; 4 using namespace std; 5 6 const int N = 100000 + 10; 7 8 namespace sam { 9 struct Node { 10 int maxl; 11 Node *par, *to[10]; 12 13 Node() {} 14 Node(int maxl) : maxl(maxl) {} 15 } pool[N * 40], *pis, *root; 16 17 void init() { 18 pis = pool; 19 root = new(pis++) Node(0); 20 } 21 22 Node *extend(Node *p, int c) { 23 if(p->to[c]) { 24 Node *q = p->to[c]; 25 if(q->maxl == p->maxl + 1) return q; /*q->right++*/ 26 Node *np = new(pis++) Node(*q); 27 np->maxl = p->maxl + 1, q->par = np; 28 for(; p && p->to[c] == q; p = p->par) p->to[c] = np; 29 return np; 30 } else { 31 Node *np = new(pis++) Node(p->maxl + 1); 32 for(; p && !p->to[c]; p = p->par) p->to[c] = np; 33 if(!p) np->par = root; 34 else { 35 Node *q = p->to[c]; 36 if(q->maxl == p->maxl + 1) np->par = q; 37 else { 38 Node *nq = new(pis++) Node(*q); 39 nq->maxl = p->maxl + 1; 40 q->par = np->par = nq; 41 for(; p && p->to[c] == q; p = p->par) p->to[c] = nq; 42 } 43 } 44 return np; 45 } 46 } 47 48 LL solve() { 49 LL res = 0; 50 for(Node *p = pool + 1; p != pis; ++p) { 51 res += p->maxl - p->par->maxl; 52 } 53 return res; 54 } 55 } 56 57 struct Edge {int to; Edge *next;} pool[N * 2], *fir[N], *pis = pool; 58 void AddEdge(int u, int v) {pis->to = v, pis->next = fir[u], fir[u] = pis++;} 59 60 int ch[N], deg[N]; 61 62 void dfs(int u, int fa, sam::Node *p) { 63 sam::Node *last = sam::extend(p, ch[u]); 64 for(Edge *p = fir[u]; p; p = p->next) { 65 int v = p->to; 66 if(v != fa) dfs(v, u, last); 67 } 68 } 69 70 int main() { 71 #ifdef DEBUG 72 freopen("in.txt", "r", stdin); 73 #endif 74 75 int n; scanf("%d%*d", &n); 76 for(int i = 1; i <= n; i++) scanf("%d", ch + i); 77 for(int i = 1; i < n; i++) { 78 int u, v; scanf("%d%d", &u, &v); 79 AddEdge(u, v), AddEdge(v, u); 80 ++deg[u], ++deg[v]; 81 } 82 sam::init(); 83 for(int i = 1; i <= n; i++) if(deg[i] == 1) { 84 dfs(i, 0, sam::root); 85 } 86 cout << sam::solve() << endl; 87 return 0; 88 }