您的位置:首页 > 其它

回文树笔记(转自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函数

代码模板:

#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棵回文树(当然一棵用完了初始化再用一次也可以的)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: