zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    java核心学习(二十七) 多线程---线程相关类
    java核心学习(二十六) 多线程---线程池
    java核心学习(二十五) 多线程---线程组和未处理的异常
    java核心学习(二十四) 多线程---线程通信
    java核心学习(二十三) 多线程---线程同步
    java核心学习(二十二) 多线程---线程控制
    模线性方程 poj2115
    求两个圆的重合面积+二分 hdu3264
    求多边形面积 HDU2036
    判断两直线是否相交 hdu1086
  • 原文地址:https://www.cnblogs.com/showson/p/5586134.html
Copyright © 2011-2022 走看看