日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

回文树笔记(转自quack_quack)

發布時間:2024/4/13 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 回文树笔记(转自quack_quack) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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][N],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[n]=-1;fail[0]=1;}int getfail(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=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棵回文樹(當然一棵用完了初始化再用一次也可以的)。

轉載于:https://www.cnblogs.com/geng4512/p/5296880.html

總結

以上是生活随笔為你收集整理的回文树笔记(转自quack_quack)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。