首先CTSC只有Silver(注意不是Sliver.....被gyz吐槽了......捂),Day2第一题由于一直以来对01串很敏感,所以马上短路,认为是个神级DP题,要用到01串的各种性质,所以80分算法都没有打,不然就Gold了.
补一下后缀自动机,比较难懂,不管是FHQ的还是CLJ的(注:WC2012没去,名额限制啊),
感谢本校的Neroysq神犇的cnblog<后缀自动机初探>举了一个简单而且很有代表性的例子(FHQ的回文串太囧了)
这个例子就是aabbabd,以此构造后缀自动机(请耐心看,因为前面几步没有体现算法)
有几点要记得:
1.由一个接受态沿Parent往前走所到的状态也是接受态
2.一个节点及其父辈的代表的串有相同的后缀
1.首先神马都没有:
此时后缀只有一个就是空串:
红字表示此节点代表最长串的长度,一个节点可能代表多个串
2.现在构建"a"的自动机,就是下面这样
现在后缀变成了这样:
3.然后以上为基础构建"aa"的自动机
现在想一下,由S或者说0号节点可以接受的后缀为空串和"a"这两个,那么现在要将"aa"和"a"这两个后缀更新到后缀自动机中,那么1号节点的后缀"a"就要加入一个字符"a",而空串也要加入字符"a"
也就是所有之前的后缀都要在后面加入一个字符"a".
但是由于1号节点之前所代表的后缀"a"和1的Parent所代表的后缀(空串)+"a"代表的一样,所以,无需更新1及之前的可接受态
如下图:
3.更新自动机变成"aab"自动机
同上加所有接受态也要调整,就是在后面加上"b"字符:
这时,由于1,2节点无法代表三个后缀的任意一个,所以除空串的所有后缀都由3代替
这时3号节点和0号节点为接受态.
自动机成了这样:
S1:新建节点3
S2:找到最后一个后缀也就是最后一个接受态是节点2
S3:2号节点直接连向3,表示插入后缀"aab"
S4:向上找2的Parent,1号节点,向3连边,表示插入后缀"ab"
S5:找到S,连边,表示插入后缀"b".
S6:没有其他接受态了,那么3的上一个接受态为S,Parent[3]=S
4:更新成"aabb"的自动机
同理,在所有接受态后加上字符"b"
不过由于接受态0(S)的转移"b"已经存在,那么,由于不能破坏原来的中间态的转移,只能新建一个节点,来代替接受态0(S)的转移节点
找到0(S)时,发现转移"b"已经有节点占了,所以新建节点5,将3号所有信息Copy(包括Parent),然后更新len值,就是node[5]->len=node[5]->parent->len+1,所以5号节点可以代表后缀空串(0号代表的串)+字符"b"=后缀"b",节点3成了中间态,所以将节点为原接受态的节点指向3的转移改为指向5,这时,我们发现指向3的原接受态节点一定是当前节点0(S)及当前未访问的原接受态节点,所以可以直接沿着Parent往上更新.
然后节点5的Parent及祖先加入了现在的接受态
再次重申一点:一个节点及其父辈的代表的串有相同的后缀,且代表串长度递减,由于5号节点是接受态,所以他的父辈也是接受态,同时反过来也一样,与任意接受态拥有相同后缀的长度小于当前节点的未访问节点一定是当前节点的父辈,如与5号节点有相同后缀的长度小于5号节点的未访问的节点一定是5号的父辈,一定可以作为接受态.
因此为了维护这个性质,我们应该将3号节点的父亲重定义为5
到这里基本上应该明白了
就将剩下的构造过程放出来:
代码什么的如下:
插入一个节点:
意义:
当前新建节点为np,最后的接受态为tail,接受态指针p,pool是内存池,就是一个很大的数组,2*n的空间吧
init就是0号节点
Step 1:建立节点np,p指向tail,np的len更新
Step 2:沿着Parent寻找第一个有转移冲突的接受态节点并沿途更新转移
Step 3:
-
如果找不到,np的Parent更新为init.
-
如果正好冲突的节点长度为当前接受态节点那么np的Parent赋为冲突的节点.
-
否则,新建节点r,Copy冲突节点所有信息,更新r->len=p->len+1,将冲突节点的Parent和np的Parent赋为r,再往前更新与冲突节点有关的转移.
至此结束..........
个人感觉应该表达还算清楚吧(=.=)......