回文树介绍看这 : 点击
回文树
首先,回文树有何功能?
假设我们有一个串S,S下标从0开始,则回文树能做到如下几点:
1.求串S前缀0~i内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
2.求串S内每一个本质不同回文串出现的次数
3.求串S内回文串的个数(其实就是1和2结合起来)
4.求以下标i结尾的回文串的个数
模板:
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
题意
求一个字符串中所有回文子串的出现次数与长度乘积的最大值
#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 中和该子串相同的子串个数的总和。
从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
题意
给出一个数字串,求其本质不同的回文子串的和。
在回文树建立的过程中自带去重,所以只需要跑一遍记录答案就好了。
奇根下直接连接的节点所代表的的都是单个字符的回文串,其他都是在两边加上同一个字符,用这个规律去生成数字求和就好了。
#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; }