求字符串集合中本质不同的公共回文子串的数量。
字符串数量
k
≤
5
k\le5
k≤5 ,字符串总长度
∑
∣
S
∣
≤
300000
\sum|S|\le 300000
∑∣S∣≤300000 。
考虑用
M
a
n
a
c
h
e
r
Manacher
Manacher 算法进行回文串相关的统计。
不难发现,当且仅当我们当前的回文右边界进行扩展时,可能出现一个新的回文子串。
因为边界最多扩展
∣
S
∣
|S|
∣S∣ 次,所以最终答案
a
n
s
≤
min
{
∣
S
∣
}
ans\le \min\{|S|\}
ans≤min{∣S∣} ,我们可以枚举每一个公共回文子串进行验证,用
H
a
s
h
Hash
Hash 进行字符串比较。
先对第一个字符串求出其所有的回文子串,然后枚举剩下的每一个字符串,保存共有的
H
a
s
h
Hash
Hash 值。
H
a
s
h
Hash
Hash 值可以存入
s
e
t
set
set 中去重。
M
a
n
a
c
h
e
r
Manacher
Manacher 算法复杂度为
O
(
n
)
O(n)
O(n) ,使用
s
e
t
set
set 去重复杂度
O
(
l
o
g
n
)
O(logn)
O(logn) ,最终复杂度
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) ,可以通过此题。
在
M
a
n
a
c
h
e
r
Manacher
Manacher 预处理(夹入#以同时处理奇偶回文串)后的字符串上,每一个可以映射到原串上的回文串都以#结尾。
最后要除去单个#,因为它映射到原串上为空串。
注意防止
H
a
s
h
Hash
Hash 碰撞,建议使用双模数。
#include
using namespace std;
template<class T>inline void read(T&x){
char c,last=' ';
while(!isdigit(c=getchar()))last=c;
x=c^48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
if(last=='-')x=-x;
}
const int MAXN=6e5+5,P1=1e9+7,P2=1e8+7,base=257;
int k;
set<long long>Set;
string s[MAXN];
int r[MAXN];
int pre1[MAXN],pre2[MAXN];
int pow1[MAXN],pow2[MAXN];
int get1(int l,int r){//获取模数1的区间Hash值
return (pre1[r]-1ll*pre1[l-1]*pow1[r-l+1]%P1+P1)%P1;
}
int get2(int l,int r){//获取模数2的区间Hash值
return (pre2[r]-1ll*pre2[l-1]*pow2[r-l+1]%P2+P2)%P2;
}
void init(){
string S="@#";
for(int i=0;i<s[1].length();++i){
S+=s[1][i];
S+="#";
}
S+="$";
int l=S.length();
pow1[0]=pow2[0]=1;
for(int i=1;i+1<l;++i){//预处理模数的幂和前缀的Hash值
pow1[i]=1ll*pow1[i-1]*base%P1;
pow2[i]=1ll*pow2[i-1]*base%P2;
pre1[i]=(1ll*pre1[i-1]*base+S[i])%P1;
pre2[i]=(1ll*pre2[i-1]*base+S[i])%P2;
}
for(int i=1,mid=0,R=0;i+1<l;++i){//Manacher
if(i<R)r[i]=min(R-i,r[2*mid-i]);
else r[i]=0;
while(S[i+r[i]]==S[i-r[i]]){
if(S[i+r[i]]=='#')Set.insert(1ll*get1(i-r[i],i+r[i])*P2+get2(i-r[i],i+r[i]));
//出现一个可能的新回文串,注意S[i+r[i]]=='#'
++r[i];
}
if(i+r[i]>R){
mid=i;
R=i+r[i];
}
}
}
void work(string s){
string S="@#";
for(int i=0;i<s.length();++i){
S+=s[i];
S+="#";
}
S+="$";
int l=S.length();
pow1[0]=pow2[0]=1;
for(int i=1;i+1<l;++i){
pow1[i]=1ll*pow1[i-1]*base%P1;
pow2[i]=1ll*pow2[i-1]*base%P2;
pre1[i]=(1ll*pre1[i-1]*base+S[i])%P1;
pre2[i]=(1ll*pre2[i-1]*base+S[i])%P2;
}
set<long long>_S;
for(int i=1,mid=0,R=0;i+1<l;++i){
if(i<R)r[i]=min(R-i,r[2*mid-i]);
else r[i]=0;
while(S[i+r[i]]==S[i-r[i]]){
long long val=1ll*get1(i-r[i],i+r[i])*P2+get2(i-r[i],i+r[i]);
if(S[i+r[i]]=='#'&&Set.count(val))_S.insert(val);//出现一个公共回文子串
++r[i];
}
if(i+r[i]>R){
mid=i;
R=i+r[i];
}
}
Set=_S;
}
int main()
{
read(k);
for(int i=1;i<=k;++i)cin>>s[i];
init();
for(int i=2;i<=k;++i)work(s[i]);
cout<<Set.size()-1<<'\n';//去掉单个#
return 0;
}