(转载)回文树/回文自动机(知识整理+板子总结)

心得

回文树,又名回文自动机,是一个东西

去年网络赛之后一直想学这个东西,一直碍于太难没学……

今天有个SA+PAM(后缀数组+回文自动机)的题,快100个队过,我就来补了……

一看我收藏的那篇讲解回文树的博客2W访问,看来你国网友都会了

原理感觉和AC自动机很类似,真的很巧妙啊,

由于now是从cur补字母c构成的回文串,

所以fail[now]要从fail[cur]失配时的最长后缀回文串状态补字母c

思路来源

https://blog.csdn.net/u013368721/article/details/42100363

知识点整理

功能:

1.求串S前缀0~i内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)

2.求串S内每一个本质不同回文串出现的次数

3.求串S内回文串的个数(其实就是1和2结合起来)

4.求以下标i结尾的回文串的个数

 

1.len[i]表示编号为i的节点表示的回文串的长度(一个节点表示一个回文串)。

2.next[i][c]表示编号为i的节点表示的回文串在两边添加字符c以后变成的回文串的编号(和字典树类似)。

3.fail[i],fail指针,失配后跳转到fail指针指向的节点

即节点i失配以后,应跳转到编号为i的节点代表的回文串的最长后缀回文串j,

若编号为k的节点保存了回文串j,则fail[i]=k

4.cnt[i]表示编号为i的节点表示的回文字符串在整个字符串中出现了多少次。

(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)

5.num[i]表示节点i表示的回文字符串中,有多少个本质不同的字符串(包括本身)。

6.last指向新添加一个字母后所形成的最长回文串表示的节点。

7.S[i]表示第i次添加的字符(一开始设S[0] = -1(可以是任意一个在串S中不会出现的字符))。

8.p表示添加的节点个数。

9.n表示添加的字符个数。

一开始回文树有两个节点,0表示偶数长度串的根和1表示奇数长度串的根,

且len[0] = 0,len[1] = -1,last = 0,S[0] = -1,n = 0,p = 2(添加了节点0、1)。

 

count函数用于计算cnt[i]

将节点x在fail指针树中将自己的cnt累加给父亲,从叶子开始倒着加,

最后就能得到串S中出现的每一个本质不同回文串的个数

 

如果只求本质不同回文串的种类数,答案就是回文树中除去虚节点0和虚节点1之外的节点数,即p-2

而所有奇回文串的根是cnt[1],偶回文串的根是cnt[0],且cnt[0]连向cnt[1]

那么虚节点cnt[1],记录的就是所有回文串出现的次数

复杂度

空间复杂度:O(N*字母种类数)

时间复杂度见到两种说法,一种说是O(N*字母种类数)

另一种说法是,长度为n的字符串本质不同的回文子串数目最多为n,所以时间复杂度:O(n)

例题

1.ural1960. Palindromes and Super Abilities

2.TsinsenA1280. 最长双回文串

3.TsinsenA1255. 拉拉队排练

4.TsinsenA1393. Palisection

5.2014-2015 ACM-ICPC, Asia Xian Regional Contest G The Problem to Slow Down You

板子整理1

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005 ;
const int N = 26 ;

char s[MAXN];
int len;

struct Palindromic_Tree {
	int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
	int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点
	int cnt[MAXN] ;//cnt[i]表示编号为i的节点表示的回文字符串在整个字符串中出现了多少次。
	//cnt[i]建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的
	int num[MAXN] ;//num[i]表示节点i表示的回文字符串中,有多少个本质不同的字符串(包括本身) 
	int v;
	int len[MAXN] ;//len[i]表示节点i表示的回文串的长度
	int S[MAXN] ;//存放添加的字符
	int last ;//指向上一个字符所在的节点,方便下一次add
	int n ;//字符数组指针
	int p ;//节点指针
 
	int newnode ( int l ) {//新建节点
		for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;
		cnt[p] = 0 ;
		num[p] = 0 ;
		len[p] = l ;
		return p ++ ;
	}
 
	void init () {//初始化
		p = 0 ;
		newnode (  0 ) ;
		newnode ( -1 ) ;
		last = 0 ;
		n = 0 ;
		S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判
		fail[0] = 1 ;
	}
 
	int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的
		while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;
		return x ;
	}
 
	void add ( int c ) {
		c -= 'a' ;
		S[++ n] = c ;
		int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置
		if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串
			int now = newnode ( len[cur] + 2 ) ;//新建节点
			fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转
			next[cur][c] = now ;
			num[now] = num[fail[now]] + 1 ;
		}
		last = next[cur][c] ;
		cnt[last] ++ ;
	}
 
	void count()//如果不count向上汇总 每个节点代表的回文串 当且仅当完全相同时才会被记次数 
	{
		for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;
		//fail[i]累加i的cnt,因为如果fail[v]=u,则u一定是套在v内的子回文串!
		//所以 u自己出现了一次 在v中出现了一次 归回去能统计出u出现的次数 
	}
}a;
int main()
{
	a.init();
	scanf("%s",s);
	len=strlen(s);
	for(int i=0;i<len;++i)
	a.add(s[i]);
	a.count();//最后一定要把fail归到根
	//才能统计出每个本质不同子串出现了多少次
	printf("%d\n",a.cnt[1]);//输出所有回文串的个数 
	//所有子串的次数都加到了虚节点上
	return 0;
} 

板子整理2(福州大学模板)

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define rep(i, a, b) for(int i=(a); i<(b); i++)
#define per(i, a, b) for(int i=(b)-1; i>=(a); i--)
#define sz(a) (int)a.size()
#define de(a) cout << #a << " = " << a << endl
#define dd(a) cout << #a << " = " << a << " "
#define all(a) a.begin(), a.end()
#define pw(x) (1ll<<(x))
#define endl "\n"
typedef long long ll;
typedef pair<int, int> pii;
typedef vector<int> vi;
typedef double db;
#define mem(a,x) memset(a,x,sizeof(a))

const int N=3e5+5;
char s[N];
int n;

inline void Put(string &s,vi &V) {
    V.clear();
    rep(i,0,sz(s)) V.pb(s[i]-'a');
}

const int M=26;
struct PAM{
    int s[N],len[N],next[N][M],fail[N],cnt[N],dep[N],id[N],no[N],last,n,p,cur,now,f[N][20]; 
	ll ans;
    inline int new_node(int _l) { mem(next[p],0); cnt[p]=dep[p]=0,len[p]=_l; return p++; }
    inline void Init() { new_node(p=0),new_node(s[0]=-1),fail[last=n=0]=1; }
    inline int get_fail(int x) { for (; s[n-len[x]-1]!=s[n]; x=fail[x]); return x; }
    inline void I(int c) { 
        c-='a',s[++n]=c,cur=get_fail(last);
        if (!next[cur][c]) {
            now=new_node(len[cur]+2);
            fail[now]=next[get_fail(fail[cur])][c];
            next[cur][c]=now;
            dep[now]=dep[fail[now]]+1; 
        }
        last=next[cur][c]; cnt[last]++; 
		id[n]=last,no[last]=n; //id[第n个字符]=第i个节点 no[第last个节点]=第n个字符 记录下在原串中的位置下标 从1开始 
    }
    inline void Insert(char s[],int op=0,int _n=0) {//_n=0正序插入 _n=1倒序插入
        if (!_n) _n=strlen(s);  if (!op) rep(i,0,_n) I(s[i]); else per(i,0,_n) I(s[i]); 
    }
    inline void count() { per(i,0,p) cnt[fail[i]]+=cnt[i]; }//短回文串fail[i]计入包含它的回文串i里fail[i]的出现次数 
    inline int Find(int x,int L) {//去找节点x的回文串长不超过l的最近祖先(可以为自己)编号 倍增lca 
        if (len[x]<=L) return x;
        per(i,0,20) if (len[f[x][i]]>L) x=f[x][i];
        return fail[x];
    }
    inline void init() {//fail树预处理倍增 
        mem(f,0);
        rep(i,0,p) f[i][0]=fail[i];
        rep(i,0,p) rep(j,1,20) f[i][j]=f[f[i][j-1]][j-1];
    }
    inline void Q() {
        count();
        rep(i,2,p) no[i]--;//为了对应0-(n-1)的下标 
    }
} TT;

int main() {
    
    while (scanf("%s",s)!=EOF) {
        n=strlen(s);
        TT.Init(),TT.Insert(s),TT.Q();
    }
    
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值