回文树笔记(转自quack_quack)
2015-12-24 22:44
204 查看
1.回文树的next[charset]指针:
b->aba
那么就这样表示:b.next[a]=aba
当然树里面肯定不能存字符串,于是就直接用下标标号代替了
2.回文树的fail指针:
跟ac自动机类似,fail指针指向当前节点的最大回文后缀
没有就指向根
3.回文树的根
有2个根,一个单根就是往下连回文串长度为奇数的节点,本身长度为-1
还有个双根就是往下连回文串长度为偶数的节点,本身长度为0
双根的fail指向单根
当然,也可以像manacher那样,aab->&a&a&b&,这样只用单根就是一棵树了。
可以树DP或者可持久化什么的。。。
4.回文树节点的域
len->表示当前节点回文串的长度,一般做回文串长度的题会用
cnt->表示当前节点被插入过多少次,一般做计数类的题会用,一般需要配合count函数
代码模板:
下面说说用这个模板怎么做回文树的题:
1.最长回文子串
很简单吧,如果要输出的话还要保存插入进去的字符的下标。
但是一般用manacher写这个题会更简单。
2.求字符串中有多少个本质不同的回文子串
例如abad,本质不同的回文字串有:a,b,d,aba,共4个。
很简单吧,其实就是看看回文树里面除了根以外有几个节点。
3.对于一个空字符串s,每次在s末尾加上一个字符,对于每次操作,求字符串中有多少个本质不同的回文子串
其实跟上面代码一样。。每插入一次就算一次ans。提出这个只是为了说明回文树是动态的。当然,如果这个s支持删除操作那么应该会用到可持久化回文树。删除就得回到之前的版本。
4.求字符串每个回文子串出现过多少次
例如abad,本质不同的回文字串有:a,b,d,aba,共4个。
a出现过2次。
b出现过1次。
d出现过1次。
aba出现过1次。
终于用到cnt数组了。
上面那个模板其实就是解决这个问题的。
就是非常简单的树DP
因为i这个节点的fail[i]就是i的最长回文后缀
假设i出现过cnt[i]次,那么fail[i]除了它自己出现cnt[fail[i]]次,还在i中出现了cnt[i]次。并且还要倒着从叶子节点往根推。
最后要输出答案的话,一般还是要保存下标
根据下标和回文串长度来按题意顺序输出cnt
5.求字符串中回文串的个数
例如abad。a出现过2次。b出现过1次。d出现过1次。aba出现过1次。
因此回文串有5个。
可以发现的是,刚才的树DP已经推到根了,那么根的cnt值就是答案。因为两个根之间有fail关系所以输出哪个根的cnt都可以。
6.求两个字符串的公共回文串的个数(2014-2015 ACM-ICPC, Asia Xian Regional Contest)
建两棵回文树,分别insert两个字符串。
然后分别从2个根开始沿着next数组下去dfs。
如果treea.next[cura][i] 和treeb.next[curb][i] 都存在
那么说明两个字符串都有相同的回文串,于是
然后递归下一层dfs(cura,curb);。
如果treea.next[cura][i] 和treeb.next[curb][i] 有一个不存在就不往下dfs了。
7.BZOJ 2565 最长双回文串
给出一个字符串,求所有子串中能分成前后两个部分都是回文串最长的子串的长度。
问题实际上是求两个这样的数组left[],right[],分别表示以某位置结尾往左或往右最长的回文子串。
这个怎么搞?
就是给出一个空字符串s,每次往s的末尾加一个字符,然后查询s里面包含末尾字符的最长的回文子串的长度。修改一下insert函数:
insert函数的返回值就是要求的。
如果从左往右加字符,得到的结果就是left[],如果从右往左加字符,得到的就是right[]。然后枚举中间点,答案是left[i]+right[i],就可以找最大值。
所以需要2棵回文树(当然一棵用完了初始化再用一次也可以的)。
b->aba
那么就这样表示:b.next[a]=aba
当然树里面肯定不能存字符串,于是就直接用下标标号代替了
2.回文树的fail指针:
跟ac自动机类似,fail指针指向当前节点的最大回文后缀
没有就指向根
3.回文树的根
有2个根,一个单根就是往下连回文串长度为奇数的节点,本身长度为-1
还有个双根就是往下连回文串长度为偶数的节点,本身长度为0
双根的fail指向单根
当然,也可以像manacher那样,aab->&a&a&b&,这样只用单根就是一棵树了。
可以树DP或者可持久化什么的。。。
4.回文树节点的域
len->表示当前节点回文串的长度,一般做回文串长度的题会用
cnt->表示当前节点被插入过多少次,一般做计数类的题会用,一般需要配合count函数
代码模板:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int MAXN=100005; const int N=26; struct Palindromic_Tree{ int next[MAXN] ,fail[MAXN],cnt[MAXN],len[MAXN],S[MAXN],last,n,p; int newnode(int l) { for(int i=0;i<N;++i)next[p][i]=0; cnt[p]=0; len[p]=l; return p++; } void init() { p=0; newnode(0); newnode(-1); last=0; n=0; S =-1; fail[0]=1; } int getfail(int x) { while(S[n-len[x]-1]!=S )x=fail[x]; return x; } void insert(int c) { c-='a'; S[++n]=c; int cur=getfail(last); if(!next[cur][c]) { int now=newnode(len[cur]+2); fail[now]=next[getfail(fail[cur])][c]; next[cur][c]=now; } last=next[cur][c]; ++cnt[last]; } void count() { for(int i=p-1;i>=0;--i)cnt[fail[i]]+=cnt[i]; } }pt; char s[MAXN]; int main() { scanf("%s",s); int n=strlen(s); pt.init(); for(int i=0;i<n;i++) pt.insert(s[i]); pt.count(); for(int i=0;i<pt.p;++i) printf("%d\n",pt.cnt[i]); }
下面说说用这个模板怎么做回文树的题:
1.最长回文子串
for(int i=0;i<pt.p;++i) ans=max(ans,pt.len[i]);
很简单吧,如果要输出的话还要保存插入进去的字符的下标。
但是一般用manacher写这个题会更简单。
2.求字符串中有多少个本质不同的回文子串
例如abad,本质不同的回文字串有:a,b,d,aba,共4个。
ans=pt.p-2;
很简单吧,其实就是看看回文树里面除了根以外有几个节点。
3.对于一个空字符串s,每次在s末尾加上一个字符,对于每次操作,求字符串中有多少个本质不同的回文子串
其实跟上面代码一样。。每插入一次就算一次ans。提出这个只是为了说明回文树是动态的。当然,如果这个s支持删除操作那么应该会用到可持久化回文树。删除就得回到之前的版本。
4.求字符串每个回文子串出现过多少次
例如abad,本质不同的回文字串有:a,b,d,aba,共4个。
a出现过2次。
b出现过1次。
d出现过1次。
aba出现过1次。
终于用到cnt数组了。
上面那个模板其实就是解决这个问题的。
就是非常简单的树DP
void count() { for(int i=p-1;i>=0;--i)cnt[fail[i]]+=cnt[i]; }
因为i这个节点的fail[i]就是i的最长回文后缀
假设i出现过cnt[i]次,那么fail[i]除了它自己出现cnt[fail[i]]次,还在i中出现了cnt[i]次。并且还要倒着从叶子节点往根推。
最后要输出答案的话,一般还是要保存下标
根据下标和回文串长度来按题意顺序输出cnt
5.求字符串中回文串的个数
例如abad。a出现过2次。b出现过1次。d出现过1次。aba出现过1次。
因此回文串有5个。
可以发现的是,刚才的树DP已经推到根了,那么根的cnt值就是答案。因为两个根之间有fail关系所以输出哪个根的cnt都可以。
6.求两个字符串的公共回文串的个数(2014-2015 ACM-ICPC, Asia Xian Regional Contest)
建两棵回文树,分别insert两个字符串。
然后分别从2个根开始沿着next数组下去dfs。
如果treea.next[cura][i] 和treeb.next[curb][i] 都存在
那么说明两个字符串都有相同的回文串,于是
cura=treea.next[cura][i]; curb=treeb.next[curb][i]; ans+=treea.cnt[cura]*treeb.cnt[curb];
然后递归下一层dfs(cura,curb);。
如果treea.next[cura][i] 和treeb.next[curb][i] 有一个不存在就不往下dfs了。
7.BZOJ 2565 最长双回文串
给出一个字符串,求所有子串中能分成前后两个部分都是回文串最长的子串的长度。
问题实际上是求两个这样的数组left[],right[],分别表示以某位置结尾往左或往右最长的回文子串。
这个怎么搞?
就是给出一个空字符串s,每次往s的末尾加一个字符,然后查询s里面包含末尾字符的最长的回文子串的长度。修改一下insert函数:
int insert(int c) { c-='a'; S[++n]=c; int cur=getfail(last); if(!next[cur][c]) { int now=newnode(len[cur]+2); fail[now]=next[getfail(fail[cur])][c]; next[cur][c]=now; } last=next[cur][c]; ++cnt[last]; return len[last]; }
insert函数的返回值就是要求的。
如果从左往右加字符,得到的结果就是left[],如果从右往左加字符,得到的就是right[]。然后枚举中间点,答案是left[i]+right[i],就可以找最大值。
所以需要2棵回文树(当然一棵用完了初始化再用一次也可以的)。
相关文章推荐
- bzoj 2190 [SDOI2008]仪仗队
- 数据泵expdp和impdp使用教程
- iOS开发数据库篇—FMDB简单介绍
- Intellij idea 配置JDK
- MySQL Workbench 使用教程
- 深度学习系列(八):自编码网络多层特征学习
- 回文树笔记(转自quack_quack)
- 微软发布Azure Stack硬件需求,Linux---vim编辑器
- 单例的创建于数据的加载
- JSP复习(二)EL表达式
- 使用Vagrant練習環境佈署
- ScrollView+ListView滚动冲突,没有滑动效果 解决办法
- Java NIO1:I/O模型概述
- cocoapods安装使用
- sublime text安装ctags定位函数
- java线程池使用
- 互联网公司的简历
- Using ActiveX Object in Qt
- 中国象棋将帅问题
- HDU-1881 毕业bg (01背包变形)