迴文樹介紹看這 : 點擊php
迴文樹html
首先,迴文樹有何功能?
假設咱們有一個串S,S下標從0開始,則迴文樹能作到以下幾點:node
1.求串S前綴0~i內本質不一樣迴文串的個數(兩個串長度不一樣或者長度相同且至少有一個字符不一樣即是本質不一樣)
2.求串S內每個本質不一樣迴文串出現的次數
3.求串S內迴文串的個數(其實就是1和2結合起來)
4.求如下標i結尾的迴文串的個數ios
模板:
const int MAXN = 100005 ; const int N = 26 ; struct Palindromic_Tree { //cnt最後count一下以後是那個節點表明的迴文串出現的次數 int next[MAXN][N] ;//next指針,next指針和字典樹相似,指向的串爲當前串兩端加上同一個字符構成 int fail[MAXN] ;//fail指針,失配後跳轉到fail指針指向的節點 int cnt[MAXN] ; //表示節點i表示的本質不一樣的串的個數(建樹時求出的不是徹底的,最後count()函數跑一遍之後纔是正確的) int num[MAXN] ; //表示以節點i表示的最長迴文串的最右端點爲迴文串結尾的迴文串個數 int len[MAXN] ;//len[i]表示節點i表示的迴文串的長度(一個節點表示一個迴文串) int S[MAXN] ;//存放添加的字符 int last ;//指向新添加一個字母后所造成的最長迴文串表示的節點。 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 () { for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ; //父親累加兒子的cnt,由於若是fail[v]=u,則u必定是v的子迴文串! } } ;
例題1.BZOJ3676
題意
求一個字符串中全部迴文子串的出現次數與長度乘積的最大值c++
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pp pair<int,int> const ll mod=1e9+7; const int maxn=3e5+50; const ll inf=0x3f3f3f3f3f3f3f3fLL; int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;} int lcm(int a,int b){return a*b/gcd(a,b);} const int N=26; struct PalTree{ int next[maxn][N];///指向的串威當前串兩端加上同一個字符構成 int fail[maxn];///fail指針,失配後跳轉的fail指針指向的結點 int cnt[maxn];///表示結點i表示的本質不一樣的串的個數(不全的最後count()跑一邊纔是正確的 int num[maxn];///表示以結點i表示的最長最長迴文串的最右端點爲爲迴文結尾的迴文串個數 int len[maxn];///len[i]表示結點i表示的迴文串長度 int S[maxn];///存放添加的字符 int last;///指向新添加一個字母后造成的最長迴文串表示的結點 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){ 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]; next[cur][c]=now; num[now]=num[fail[now]]+1; } last=next[cur][c]; cnt[last]++; } void count(){ for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i]; } }pat; char s[maxn]; int main(){ std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0); cin>>s; int len=strlen(s); pat.init(); for(int i=0;i<len;i++){ pat.add(s[i]); } pat.count(); ll ret=0; for(int i=1;i<pat.p;i++){ ret=max(ll(1LL*pat.len[i]*pat.cnt[i]),ret); } cout<<ret<<endl; return 0; }
例題2 UVA7041
題意
給出兩個僅包含小寫字符的字符串 A 和 B ;
求:對於 A 中的每一個迴文子串,B 中和該子串相同的子串個數的總和。markdown
從0和1兩個根節點DFS下去,若是兩個相同的節點同時存在就統計答案。網絡
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pp pair<int,int> const ll mod=1e9+7; const int maxn=4e5+50; const ll inf=0x3f3f3f3f3f3f3f3fLL; int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;} int lcm(int a,int b){return a*b/gcd(a,b);} const int N=26; struct PalTree{ int next[maxn][N];///指向的串威當前串兩端加上同一個字符構成 int fail[maxn];///fail指針,失配後跳轉的fail指針指向的結點 int cnt[maxn];///表示結點i表示的本質不一樣的串的個數(不全的最後count()跑一邊纔是正確的 int num[maxn];///表示以結點i表示的最長最長迴文串的最右端點爲爲迴文結尾的迴文串個數 int len[maxn];///len[i]表示結點i表示的迴文串長度 int S[maxn];///存放添加的字符 int last;///指向新添加一個字母后造成的最長迴文串表示的結點 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){ 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]; next[cur][c]=now; num[now]=num[fail[now]]+1; } last=next[cur][c]; cnt[last]++; } void count(){ for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i]; } }pat1,pat2; ll dfs(int a,int b){ ll ret=0; for(int i=0;i<N;i++)if(pat1.next[a][i]!=0&&pat2.next[b][i]!=0) ret+=(ll)pat1.cnt[pat1.next[a][i]]*pat2.cnt[pat2.next[b][i]] +dfs(pat1.next[a][i],pat2.next[b][i]); return ret; } char s1[maxn],s2[maxn]; int main(){ std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0); int t,cas=0; cin>>t; while(t--){ cin>>s1>>s2; pat1.init(); pat2.init(); int len1=strlen(s1); int len2=strlen(s2); for(int i=0;i<len1;i++)pat1.add(s1[i]); for(int i=0;i<len2;i++)pat2.add(s2[i]); pat1.count();pat2.count(); ll ret=dfs(0,0)+dfs(1,1); cout<<"Case #"<<++cas<<": "; cout<<ret<<endl; } return 0; }
例題3ACM-ICPC 2018 南京賽區網絡預賽 Skr
題意
給出一個數字串,求其本質不一樣的迴文子串的和。函數
在迴文樹創建的過程當中自帶去重,因此只須要跑一遍記錄答案就行了。post
奇根下直接鏈接的節點所表明的的都是單個字符的迴文串,其餘都是在兩邊加上同一個字符,用這個規律去生成數字求和就行了。spa
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pp pair<int,int> const ll mod=1e9+7; const int maxn=2e6+50; const ll inf=0x3f3f3f3f3f3f3f3fLL; int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;} int lcm(int a,int b){return a*b/gcd(a,b);} const int N=10; ll modpow(ll a,ll b){ ll ans=1; while(b){ if(b&1)ans=(ans*a)%mod; b>>=1; a=(a*a)%mod; } return ans; } struct PalTree{ int next[maxn][N];///指向的串威當前串兩端加上同一個字符構成 int fail[maxn];///fail指針,失配後跳轉的fail指針指向的結點 int cnt[maxn];///表示結點i表示的本質不一樣的串的個數(不全的最後count()跑一邊纔是正確的 int num[maxn];///表示以結點i表示的最長最長迴文串的最右端點爲爲迴文結尾的迴文串個數 int len[maxn];///len[i]表示結點i表示的迴文串長度 int S[maxn];///存放添加的字符 int last;///指向新添加一個字母后造成的最長迴文串表示的結點 int n;///表示添加的字符個數 int p;///表示添加的結點個數 ll sum[maxn]; int newnode(int l){///新建結點 for(int i=0;i<N;i++)next[p][i]=0; cnt[p]=0; num[p]=0; len[p]=l; sum[p]=0; 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){ while(S[n-len[x]-1]!=S[n])x=fail[x]; return x; } void add(int c){ c-='0'; 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]; next[cur][c]=now; num[now]=num[fail[now]]+1; sum[now]=(sum[cur]*10*1LL)%mod; sum[now]=(sum[now]+c)%mod; if(len[cur]>=0)sum[now]=(sum[now]+(c*modpow(10*1LL,len[now]-1))%mod)%mod; } last=next[cur][c]; cnt[last]++; } void count(){ for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i]; } }pat; char s[maxn]; int main(){ std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0); cin>>s; pat.init(); int len=strlen(s); for(int i=0;i<len;i++)pat.add(s[i]); ll anw=0; for(int i=0;i<pat.p;i++)anw=(anw+pat.sum[i])%mod; cout<<anw<<endl; return 0; }
例題4 HIHO#1602 : 本質不一樣的迴文子串的數量
給定一個字符串S,請統計S的全部子串中,有多少個本質不一樣的迴文字符串?
cin>>s; pat.init(); int len=strlen(s); for(int i=0;i<len;i++)pat.add(s[i]); ll anw=0; pat.count(); cout<<pat.p-2<<endl; return 0;
例題5 BZOJ 2565 最長雙迴文串
#include <bits/stdc++.h> using namespace std; const int maxn = 3e5 + 7; const int MAXN = 100005 ; const int N = 26 ; struct Palindromic_Tree { //cnt最後count一下以後是那個節點表明的迴文串出現的次數 int next[MAXN][N] ;//next指針,next指針和字典樹相似,指向的串爲當前串兩端加上同一個字符構成 int fail[MAXN] ;//fail指針,失配後跳轉到fail指針指向的節點 int cnt[MAXN] ; //表示節點i表示的本質不一樣的串的個數(建樹時求出的不是徹底的,最後count()函數跑一遍之後纔是正確的) int num[MAXN] ; //表示以節點i表示的最長迴文串的最右端點爲迴文串結尾的迴文串個數 int len[MAXN] ;//len[i]表示節點i表示的迴文串的長度(一個節點表示一個迴文串) int S[MAXN] ;//存放添加的字符 int last ;//指向新添加一個字母后所造成的最長迴文串表示的節點。 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 ; } int 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] ++ ; return len[last]; } void count () { for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ; //父親累加兒子的cnt,由於若是fail[v]=u,則u必定是v的子迴文串! } } t; int len[maxn]; int main(int argc, char const *argv[]) { string str; cin >> str; t.init(); int maxx = 0; for(int i = 0;i < str.size(); i ++) len[i] = t.add(str[i]); t.init(); for(int i = str.size() - 1;i > 0;i --) maxx = max(maxx,t.add(str[i]) + len[i-1]); cout << maxx << endl; return 0; }