回文树

(没有坑怎么填?)

最近膜了一些关于回文串的题目,感到非常有意思,遂开篇记录.

在逛UOJ的题目时发现了vfk添上了新题,APIO 2014的题目.本身是一件很正常的事,而它事实上也没有变成什么了不得的事.我看到了Palindrome这个标题---回文串已经烂大街了,没什么新意.不过我很早就向学习回文树这东西了,久仰其大名而未尝真正去了结果它,于是我就顺手撸了一把豪哥的论文,发现他讲解的实在是晦涩难懂---论文的通病,就是虽然表述没有歧义,但是难以理解.嘛,然后我就找了几个标程,发现回文树这东西还是挺清晰的,其实非常易于理解和记忆,代码也很短.

回文树,准确的说是回文Trie+一大堆奇怪的buff,是一种奇葩数据结构.它的节点都是压缩的,并没有记录真实的值,值都记录在边上---这与Trie类似.它将Trie给对称地在前面赋值一份:比如ab->baab,ba->abba,cc->cccc,等等.这样就可以表示出所有的回文串了.

等等,这样的回文串长度不都是偶数么?

不知您是否听说过,有些时候我们可以在控制台中用[BKS]---这个特殊字符来掩盖掉前面的

字并退格.其实这是打字机时代的遗留物了,具体的我们也不深究了.这个字符要显示,当然是要占空间的.一般的字符占1格(或两格,汉字等较宽字符).有些(附加标记)是不占位置的,于是用这些东西我们可以看到诸如(屏幕上出了污点)之类的文本.但是[BKS]更特殊:它占-1格,直接退格了.

说到这里,大家大约明白了.我们可以用一种特殊的节点,表示[BKS],减去一格,那么长度不就是奇数了?我就是这么任性!

那么这就是一棵回文树了.大家能看到,回文树其实是两棵树组成的,一棵数储存长度为奇数的回文串,一棵储存长度为偶数的字符串.

于是如何构造一棵回文树?

初始的时候,一棵空树应该只包含两格节点:`''`和`-1`.

为了在线构造,我们引入一些AC自动机中的记号:Fail指针,表示当匹配失败时调到的节点.

让我们回顾AC自动机中Fail的定义:匹配到串的后缀的最长公共前缀的尾.

这里的定义非常类似:已有的回文串中最长的为当前回文串的后缀的串.

像AC自动机中[acaaac]中第二个c的fail应指向第一个c

像回文串中[abaaaba]的abaaaba的fail应指向aba.

这两者之间有着惊人的相似性.

那为什么AC自动机不能在线构造? 由于AC自动机要加入多个串,fail有改变的可能性.

但是注意到回文串是对称的,这意味着在加入一个回文串之前它的fail一定已经加入了,否则也不会是它的fail了.

那么我们现在有了Fail指针.

next数组参照Trie就行了.只不过Trie.next[P(A)][c]=P(Ac),PalTree.next[P(A)][c]=P(cAc),也就是两端都加上一个边所表示的字符.

为了构造回文树,我们还需要一个last指针,指向最近加入的回文串在回文树中的位置.

初始时last指向`''`节点.

每加入一个字符,我们都要寻找加入的回文串.有可能这个回文串并不在树中,那我们抖一下机灵,寻找以上一个字符结尾的回文串使得其非加入字符的另一端的字符为加入字符这端的字符.显然这些串应该都在fail链上,那我们只需要顺着fail链找即可.

然后又长得像萌萌哒AC自动机惹.AC自动机是这么判断终结条件的:假设fail链找到了这么一个后缀节点D,使得(x为新加入的字符)Dx!=NULL

而PalTree是这样的:[p]Dx中p==x.

那么我们找到了fail后怎么办呢?如果已经加入过这个回文串了那就无视它好了,只需要把count+=1

没找到就要新建一个节点,把next[fail][x]设置为这个节点,然后设置新节点的fail为顺着fail链找下去,直到有的那个节点.还是把count+=1

然后我们就可以把last设置称为这个节点了.

滚代码

我的题目 回文密度 标程

#include "cstdio"
#include "algorithm"
#include "cstring"
#define maxn 10000005
#define sigma 26
struct ptree{
	char s[maxn];
	int next[maxn][sigma],fail[maxn],cnt[maxn],len[maxn];
	int last,n,p;
	long long res;
	inline int newnode(int l){
		cnt[p]=0;
		len[p]=l;
		return p++;
	}
	inline void init(){
		newnode(0),newnode(-1);//two roots
		s[n]=-1;
		fail[0]=1;
	}
	inline int FL(int x){
		while(s[n-len[x]-1]!=s[n]) x=fail[x];
		return x;
	}
	void add(char c){
		c-='a';
		s[++n]=c;
		int cur=FL(last);
		if(!next[cur][c]){
			int now=newnode(len[cur]+2);
			fail[now]=next[FL(fail[cur])][c];
			next[cur][c]=now;
		}
		last=next[cur][c];
		++cnt[last];
	}
	inline unsigned long long count(){
		unsigned long long pk=0;
		for(int i=p-1;~i;--i){
			cnt[fail[i]]+=cnt[i];
			pk+=((unsigned long long)cnt[i])*len[i];
		}
		return pk;
	}
} p;
char s[maxn];
int i,j,k;
int main(){
	scanf("%s",s);
	j=strlen(s);
	p.init();
	for(i=0;i<j;++i) p.add(s[i]);
	printf("%llu\n",p.count());
	return 0;
}

APIO2014 Palindromes

#include "cstdio"
#include "algorithm"
#include "cstring"
#define maxn 300005
#define sigma 26
struct ptree{
	char s[maxn];
	int next[maxn][sigma],fail[maxn],cnt[maxn],len[maxn];
	int last,n,p;
	long long res;
	inline int newnode(int l){
		cnt[p]=0;
		len[p]=l;
		return p++;
	}
	inline void init(){
		newnode(0),newnode(-1);//two roots
		s[n]=-1;
		fail[0]=1;
	}
	inline int FL(int x){
		while(s[n-len[x]-1]!=s[n]) x=fail[x];
		return x;
	}
	void add(char c){
		c-='a';
		s[++n]=c;
		int cur=FL(last);
		if(!next[cur][c]){
			int now=newnode(len[cur]+2);
			fail[now]=next[FL(fail[cur])][c];
			next[cur][c]=now;
		}
		last=next[cur][c];
		++cnt[last];
	}
	inline long long count(){
		long long pk;
		for(int i=p-1;~i;--i){
			cnt[fail[i]]+=cnt[i];
			pk=((long long)cnt[i])*len[i];
			if(pk>res) res=pk;
		}
		return res;
	}
} p;
char s[maxn];
int i,j,k;
int main(){
	scanf("%s",s);
	j=strlen(s);
	p.init();
	for(i=0;i<j;++i) p.add(s[i]);
	printf("%lld\n",p.count());
	return 0;
}

CodeChef April Lunchtime http://www.codechef.com/problems/PALPROB

#include <cstdio>
#include <cstring>
#include <iostream>
#define maxn 100005
struct palt{
	char s[maxn];
	int next[maxn][26],fail[maxn],cnt[maxn],len[maxn],palness[maxn];
	int hfail[maxn];
	int last,p,n;
	inline int newnode(int l){
		len[p]=l;
		cnt[p]=0;
		return p++;
	}
	inline void init(){
		memset(s,0,sizeof s);
		memset(next,0,sizeof next);
		memset(fail,0,sizeof fail);
		memset(hfail,0,sizeof hfail);
		memset(cnt,0,sizeof cnt);
		memset(len,0,sizeof len);
		memset(palness,0,sizeof palness);
		last=p=n=0;
		newnode(0),newnode(-1);
		s[n]=-1;
		hfail[0]=fail[0]=1;
	}
	inline int FL(int x){
		while(s[n-len[x]-1]!=s[n]) x=fail[x];
		return x;
	}
	void add(char c){
		c-='a';
		s[++n]=c;
		int cur=FL(last);
		if(!next[cur][c]){
			int now=newnode(len[cur]+2);
			fail[now]=next[FL(fail[cur])][c];
			hfail[now]=next[FL(hfail[cur])][c];
			while(len[hfail[now]]<<1>len[now]) hfail[now]=fail[hfail[now]];
			next[cur][c]=now;
			palness[now]=1+palness[hfail[now]]*(len[now]/2==len[hfail[now]]);
		}
		last=next[cur][c];
		++cnt[last];
	}
	inline unsigned long long count(){
		unsigned long long pq=0;
		for(int i=p-1;i>1;--i){
			cnt[fail[i]]+=cnt[i];
			pq+=((long long)palness[i])*cnt[i];
		}
		return pq;
	}
} PalTree;
int n,i,j,k;
char s[100005];
int main(){
	scanf("%d",&n);
	while(n--){
		memset(s,0,sizeof s);
		scanf("%s",s);
		i=strlen(s);
		PalTree.init();
		for(j=0;j<i;++j) PalTree.add(s[j]);
		std::cout<<PalTree.count()<<std::endl;
	}
	return 0;
}

(一不小心刷到Rank#1真是有点不好意思呢)

posted @ 2015-05-10 00:40  zball  阅读(1658)  评论(0编辑  收藏  举报