迴文自動機——字符串的剋星

首先,迴文樹有何功能?
假設我們有一個串S,S下標從0開始,則迴文樹能做到如下幾點:
1.求串S前綴0~i內本質不同迴文串的個數(兩個串長度不同或者長度相同且至少有一個字符不同便是本質不同)
2.求串S內每一個本質不同迴文串出現的次數
3.求串S內迴文串的個數(其實就是1和2結合起來)
4.求以下標i結尾的迴文串的個數

建立的迴文樹代表的都是以s[i]結尾的最長迴文串。

(因此cnt數組原本表示的是以某以字符串結尾的本質不同(迴文串相同,位置不同 即同種迴文串)串的個數(建樹時求出的不是完全的,最後count()函數跑一遍以後纔是正確的)

迴文樹每一個節點都代表一種字符串

1.len[i]表示編號爲i的節點表示的迴文串的長度(一個節點表示一個迴文串)
2.tree[i][c]表示編號爲i的節點表示的迴文串在兩邊添加字符c以後變成的迴文串的編號(和字典樹類似)。
3.fail指針指向以該字符結尾的次長會文串(和AC自動機類似)。
4.cnt[i]表示節點i表示的本質不同(同種)的迴文串的個數(建樹時求出的不是完全的,最後count()函數跑一遍以後纔是正確的)
5.num[i]表示以節點i表示的最長迴文串的最右端點爲迴文串結尾的迴文串種類數(也就是fail指針路徑的深度)。(我感覺是不同種類的字符串的個數,因爲num[now] = num[fail[now]] + 1(以這個串結尾的迴文串的種類)
6.last:上一次添加字符時形成迴文串在迴文樹中的節點座標。
7.s[i]表示第i次添加的字符(一開始設s[0] = -1(可以是任意一個在串S中不會出現的字符))。
8.tot表示添加的節點個數。
9.n表示添加的字符個數。

一開始迴文樹有兩個節點,0表示偶數長度串的根和1表示奇數長度串的根,且len[0] = 0,len[1] = -1,last = 0,S[0] = -1,n = 0,p = 2(添加了節點0、1)

具體思路:
Palindromic Tree——迴文樹

模板

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5;
//fail指針指向以該字符結尾的次長會文串
//next數組用來存放回文樹
//len數組用來存儲樹中每一個節點回文串的長度
//S數組用來記錄添加過的字符,方便以後通過len數組判斷是否是迴文串。

/*cnt[i]:
節點i表示的本質不同的串的個數(建樹時求出的不是完全的,count()加上子節點以後纔是正確的) */
int N;
string S;
struct node{
	int len[maxn],tree[maxn[26],fail[maxn],s[maxn],cnt[maxn],num[maxn];
	int tot,n,last;
	
	int newnode(int l)
	{
		len[tot]=l;
		return tot++;return tot++;//勿打成++p,因爲此節點爲p,我們應返回p
	}
	void init()
	{
		tot=n=last=0;
		fill(num,num+maxn,0);
		fill(tree[0],tree[0]+maxn*26,0);
		fill(cnt,cnt+maxn,0);
		newnode(0);
		newnode(-1);
		s[0]=-1;//S數組用來保存添加過的字符,這個保存時一定要從1開始,不然會出現問題。
		fail[0]=1;
		fail[1]=1;//這個寫不寫都無所謂,因爲len[1]=-1,這個一定成立,不會再向下找fail了。
	}
	
	//這個函數使用來找最長迴文後綴的函數。
	int get_fail(int x)
	{
		while(s[n-len[x]-1]!=s[n]) x=fail[x];
		return x;
	}
	void insert(int c)
	{
		c-='a';
		s[++n]=c;
		int cur=get_fail(last);//能與該插入字符構成迴文串的節點。
		if(!tree[cur][c])
		{
			int now=newnode(len[cur]+2);
			fail[now]=tree[get_fail(fail[cur])][c];
			tree[cur][c]=now;
			num[now] = num[fail[now]] + 1 ;
		}
		last=tree[cur][c];
		cnt[last]++;
		
	}
	//統計本質相同的迴文串的出現次數
	//逆序累加,保證每個點都會比它的父親節點先算完,於是父親節點能加到所有子孫
	void count()
	{
		//原本求得是以節點i結尾的最長的迴文串出現的次數,所以這個
		//最長串中一定包含有短的迴文串。
		 //兒子累加父親的cnt,因爲如果fail[v]=u,則u一定是v的子迴文串! 
		for(int i=tot-1;i>=0;i++)
		{
			cnt[fail[i]]+=cnt[i];
		}
	}	
	
}run;

int main()
{
	string S;
	cin>>S;
	run.init();
	N=S.size();
	for(int i=0;i<N;i++)
	{
		run.insert(S[i]);
	}
	run.count();
	
	
}

迴文樹例題

迴文樹練習題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章