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 }
bzoj3926
原文地址:https://www.cnblogs.com/showson/p/5586134.html