回文树或者回文自动机,及相关例题
2017-06-02 20:44
190 查看
回文树简述
在大部分说法中,回文树与回文自动机指的是一个东西;回文树是对一个字符串,基于自动机思想构建的处理回文问题的树形结构;
回文树是对着一个单串建立的;
于是他主要用于计数(回文子串种类及个数)
基本建立思路是建立其前缀的回文树,然后每加上一个字符,统计产生了什么回文;
回文树存在fail指针但一般不承接字符串匹配问题;
(回文树大概可以判定一个回文串是不是一个串的子串,但KMP之类的可以做得更好)
构建好的回文树,是这样的:
(好难看)
(点“偶”的fail指向点“奇”)
可看出:
存在两个树结构,分别记录奇数|偶数长度的回文;
每个点记录一种字符串(但是由于可以通过根到该节点的路径确定这个字符串是什么,于是并不需要真的在该节点记录/*写下*/这个信息)
每个节点连字符x边向一个子节点,表示在她的左右两边加x构成的回文是这个总字符串的子串(根节点相关部分单独定义);
每个节点连一条fail指针向其最长后缀回文;
几个性质
|s|表示s串的长度;节点数不超过|s|:每个节点是互不相同的回文,每个回文都是某一个点的最长后缀回文,而每个点的最长后缀回文是唯一的;
转移数是O(|s|)级的:树有节点数-2个边(因为是两棵树),加上每个节点一个fail,于是是O(|s|)级的;
构造方法
回文树的基础插入算法:建立s的回文树;
现在已建立了s的前缀x,然后考虑插入下一个字符;
每次插入最多只会把她的最长后缀回文贡献到树中;
证明:
c除最长后缀回文之外的后缀回文i是其最长后缀回文k的子串,则她关于k的中心有一个对称串j,由于k本身是对称的,于是j与i本质相同,由于j已经加入回文树中,于是i不必加入;
那么我们设x串之前的最长后缀回文是t,在加入c后,我们期望的c的最长后缀回文最长是ctc,
但t串前面不是字符c怎么办,
试试t的除自己外最长回文后缀的前一个字符是不是c,
若不行,再试试她的最长回文后缀。。。。
这样找的的回文如果还没存在与回文树中,则将其插入;
为了方便这一过程,我们维护每点一个指针fail,s当前的前缀x的最长回文后缀t,没有ctc,就跳fail,直到可以匹配;
建fail的过程与匹配过程类似;
然后维护一个last指向当前在的节点,以便插入新的回文;
通过势能分析法发现这个算法是每次插入均摊O(1),总共O(n)的;(反正我也不会)
例题
BZOJ P2565 最长双回文串对读入的字符串,正反分别建回文树,在此同时分别记录以每个为起点|终点的最长前|后缀回文;
枚举断点,求最值即可;
#include<cstdio> #include<cstring> using namespace std; struct dt{ int ch[30],fail,len; }; struct Pld_T{ int tot,last; dt data[500500]; int len[500500]; char s[500500]; void Init(){ data[0].fail=1; data[1].len=-1; tot=1;last=0; } void insert(int x){ int j; while(s[x-data[last].len-1]!=s[x])last=data[last].fail; if(!data[last].ch[s[x]-'a']){ data[++tot].len=data[last].len+2; j=data[last].fail; while(s[x-data[j].len-1]!=s[x])j=data[j].fail; data[tot].fail=data[j].ch[s[x]-'a']; data[last].ch[s[x]-'a']=tot; last=tot; } else last=data[last].ch[s[x]-'a']; len[x]=data[last].len; } }; Pld_T pld_t1,pld_t2; int main() { int i,j,k,len,ans=0; pld_t1.Init();pld_t2.Init(); scanf("%s",pld_t1.s+1); len=strlen(pld_t1.s+1); for(i=len,j=1;i>=1;i--,j++) pld_t2.s[j]=pld_t1.s[i]; pld_t2.s[0]=pld_t1.s[0]='#'; pld_t2.s[len+1]=pld_t1.s[len+1]='\0'; for(i=1;i<=len;i++){ pld_t1.insert(i); pld_t2.insert(i); } for(i=1;i<len;i++) if(ans<pld_t1.len[i]+pld_t2.len[len-i]) ans=pld_t1.len[i]+pld_t2.len[len-i]; printf("%d",ans); return 0; }
BZOJ P3676 [Apio2014]回文串
对每个点维护cnt表示她是几个字符的最长回文后缀;
那么他出现的次数就是她作为自己的末端字符的最长回文后缀出现的次数,以及她作为自己的末端字符的最长回文后缀的回文后缀(也不比是除她外最长的)出现的次数;
见代码
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; char s[300010]; struct Pld_T{ int ch[26],fail,len,cnt; }; Pld_T pld_t[300010]; int tot; int main() { int i,j,k,len; long long ans=0; scanf("%s",s+1); len=strlen(s+1);s[0]='#'; pld_t[0].fail=1;k=0;pld_t[1].len=-1;tot=1; for(i=1;i<=len;i++){ while(s[i-pld_t[k].len-1]!=s[i])k=pld_t[k].fail; if(!pld_t[k].ch[s[i]-'a']){ pld_t[++tot].len=pld_t[k].len+2; j=pld_t[k].fail; while(s[i-pld_t[j].len-1]!=s[i])j=pld_t[j].fail; pld_t[tot].fail=pld_t[j].ch[s[i]-'a']; pld_t[k].ch[s[i]-'a']=tot; } k=pld_t[k].ch[s[i]-'a']; pld_t[k].cnt++; } for(i=tot;i>=2;i--){ pld_t[pld_t[i].fail].cnt+=pld_t[i].cnt; if((long long)pld_t[i].cnt*pld_t[i].len>ans) ans=(long long)pld_t[i].cnt*pld_t[i].len; } printf("%lld",ans); return 0; }
我之前想在fail树上建LCT来着,呵呵呵哈哈哈哈!!!!!!!!!!
相关文章推荐
- 回文树(回文自动机)学习小结
- Palindromic Tree 回文自动机-回文树
- Palindromic Tree 回文自动机-回文树 例题+讲解
- 回文树(回文自动机) - BZOJ 3676 回文串
- 关于一道J笔试或者机试题的Java实现:从键盘输入一串字符,翻转后输出(要求不使用string相关类即对象)
- 在MySchool数据库中有俩个表:Student和Score(相关例题)
- 通过产品ID或者SKU获取产品相关信息(ShortDescription、Name、Price、ProductUrl、ImageUrl)
- 【BZOJ3676】[Apio2014]回文串 【回文自动机】
- 回文自动机
- 使用原始XML资源保留配置信息或者相关数据资源
- 【XSY2715】回文串 树链剖分 回文自动机
- 请确保二进制储存在指定的路径中,或者调试他以检查该二进制或相关的DLL文件
- BZOJ 2565 最长双回文串(回文自动机)
- 大数据DDos检测——DDos攻击本质上是时间序列数据,t+1时刻的数据特点和t时刻强相关,因此用HMM或者CRF来做检测是必然! 和一个句子的分词算法CRF没有区别!
- 如何收集和EMC存储相关的主机日志(EMC Report或者EMC Grab)?
- 回文串问题的克星——Palindrome Tree(回文树)/Palindrome Automaton(回文自动机)学习小记
- JAVA高精度数值运算方法,小数点后保留位数,结合相关例题进行介绍!
- 例题3-3 回文词(Palindromes,UVa401)
- 论如何优雅的处理回文串 - 回文自动机详解.
- [BZOJ4044]Virus synthesis 回文自动机的DP