关于回文树的理解
关于回文树的理解
前言
这段时间搞字符串上了瘾?
看起来是的
那就继续搞吧
Part1一些名词
回文串
不想解释什么意思
回文子串
一个串的子串,它是回文串,那么它就是回文子串
最长回文后缀
对于一个长度小于自己的后缀,如果它是回文串,并且不存在比它更长的回文后缀,那么它就是最长回文后缀
最长回文前缀
基本和上面一样
Part2 回文树的形态
长成啥样啊?
我们很容易知道,回文串有两种,一种长度是奇数,一种长度是偶数
而在回文树上走,我们肯定不是一次只在后面添加一个字符
显然是在前后各添加一个字符
所以我们不难得出一点,如果串可以变成另外一个回文串
那么它的长度一定加上了一个偶数
所以在回文树上,为了区分这两种不同的回文串
所以回文树相当于一个森林
有两棵树,一棵的代表长度为奇数的回文串,另一棵代表长度为偶数的回文串
就像后缀自动机,Trie树,AC自动机这些东西一样
每一个节点代表的都是一个(些)串
回文树的每个节点也是代表着一个串
对于每个点的转移,比如说
对于某个点代表的回文串"aba"
假如它有一个'c'的转移
那么,"aba"就会指向一个代表着"cabac"的串
同样的,类似于AC自动机有\(fail\),后缀自动机有\(parent\)
当失配的时候回文树也有\(fail\)向上跳
那么,我们来考虑一些这个东西是什么?
假设当前加入的位置是\(r\)
如果之前已经匹配出了一个回文\(S_{l..r-1}\)
那么,如果有\(S_{l-1}=S_r\)就没有失配
如果失配了,因为\(r\)位置是不能变动的
所以挪动的只有\(l\)位置
而\(S_{l..r}\)显然也要是一个回文串
所以\(l\)挪动到的位置就是\(S_{l..r-1}\)的最长回文后缀的开始位置
一些小小的结论
综上所述,我们知道了两点:
1.对于回文树上的两个节点,如果存在字符c的连边,那么,就会从串x,变成cxc
2.对于回文树上的失配(fail)指针,指向这个点所代表的字符串的最长回文后缀所在的节点
一些小小的证明
接下来,我们还可以知道几点
1.对于任何一个串S,它的本质不同的回文串的个数不会超过|S|个
2.如果在串S后面加入一个字符,新增的本质不同的回文串的个数不会超过1个
怎么证明?
利用数学归纳法来证明
当\(|S|=1\)时,显然成立
如果我们知道\(|S|=x-1\)时成立,现在插入\(x\)位置,字符为\(c\)
如果以\(x\)位置结尾出现了两个新的本质不同的回文串
假设较长的从\(l1\)开始,较短的从\(l2\)开始
因为\(|S_{l1..r}|>|S_{l2..r}|\)
又根据回文串对称的性质
所以\(S_{l2..r}\)在\(S_{l1..l1+r-l2}\)必定出现过
所以不存在两个本质不同的回文串
所以最多新增一个本质不同的回文串
所以到\(x\)位置出现的本质不同的回文串的个数最多为\(x\)个
同时,我们也证明了每次插入一个新的字符,最多增加一个本质不同的回文子串
Part3 回文树的构造
看完了上面,应该就知道了回文树上的东西代表着什么
我们的构造采用增量法,也就是类似于后缀自动机的\(extend\)
假设前面已经构造出了\(1..x-1\)的回文树,现在要加入第\(x\)个字符\(c\)
因为要接在\(x-1\)的后面,我们又知道最多一个产生一个新的本质不同的回文串
也就是\(S_{1..x-1}\)中,最长的某个回文后缀\(S_{l..x-1}\),
同时能够满足\(S_{l-1}=S_x\)
因为只需要不停地寻找最长回文后缀
根据回文树上的\(fail\)指针的含义
我们很容易知道知道,
只需要从上一个位置添加完之后的最后一个位置
(也就是以\(x-1\)为结束位置的最长回文子串)
所代表的节点开始,沿着\(fail\)一路上跳
检查是否满足\(S_{l-1}=S_x\)就行了
假设这样找到的一个位置是\(p\)
不难证明这个位置\(p\)一定存在(为啥?长度为1的回文串呀)
如果\(p.son[c]\)也就是连边\(c\)已经存在
那就什么都不用干,因为这个回文子串已经存在过
不需要重新建边
否则,重新建一个点表示这个回文子串,假设点是\(np\)吧
然后\(p.son[c]=np\)
现在我们要找\(np\)的\(fail\)啦
因为要找的是最长回文后缀,不能是自己
所以令\(k=p.fail\)
然后就像前面一样的,找到第一个满足\(S[l_k-1]=S[n]\)的点
让\(k\)沿着\(fail\)向上跳
然后\(np.fail=k\),表示找到啦
这样,我们的回文树就利用增量法构建出来啦
当然,两棵树的根节点的长度分别是\(-1\)和\(0\)
然后为\(0\)的根节点的\(fail\)连向\(-1\)的根节点
\(-1\)个根节点的\(fail\)也连向自己
为啥?自己想
初始情况下的\(last=0,tot=1\)(代表什么可以参考程序)
这是一棵回文树
struct Palindromic_Tree
{
struct Node
{
int son[26];
int ff,len;
}t[MAX];
int last,tot;
void init()
{
t[++tot].len=-1;
t[0].ff=t[1].ff=1;
}
void extend(int c,int n)
{
int p=last;
while(s[n-t[p].len-1]!=s[n])p=t[p].ff;
if(!t[p].son[c])
{
int v=++tot,k=t[p].ff;
t[v].len=t[p].len+2;
while(s[n-t[k].len-1]!=s[n])k=t[k].ff;
t[v].ff=t[k].son[c];
t[p].son[c]=v;
}
last=t[p].son[c];
size[t[p].son[c]]++;
}
};
Part4 后记
这篇博客十分简短
因此肯定有很多很多不严谨的地方
更加详细的请参考
国家集训队\(2017\)年的论文
当然了,这些东西也只是我自己的理解
而回文树也有很多很神奇的用法,
等我做了一些题之后我会再回来写的。