【题目】
原题地址
题目大意:给出一个字符串,求与这个字符串【后缀树同构】的字符串有多少个。
【题目分析】
寻找后缀树的性质!
【解题思路】
这道题是真的真的很难写。
首先明确一下,后缀树是由字符串每个后缀建出的Trie。设每个后缀
s
u
f
i
suf_i
sufi为
s
[
i
.
.
n
]
s[i..n]
s[i..n]
观察后缀树的特点,我们可以发现,字符串中出现了多少个字符,根节点就有多少个分叉,也就是说最后一定是乘上一个组合数的形式。
接着观察后缀树的叶子节点,可以发现,深度一定有 n , n − 1 , n − 2 n,n-1,n-2 n,n−1,n−2…直到 n − i n-i n−i深度的叶子没有出现。这说明 s u f n − i + 1 suf_{n-i+1} sufn−i+1一定覆盖在了 s u f 1 suf_1 suf1到 s u f n − i suf_{n-i} sufn−i中的某一个上。同时我们可以显然看出,如果我们确定 s u f n − i + 1 suf_{n-i+1} sufn−i+1覆盖在哪个后缀上,整颗后缀树的形态一定是确定的,因此我们只用考虑最长的没有出现在后缀树中的后缀。想找到它的位置,我们可以直接用后缀数组,找到第一个 h e i g h t = = l e n height==len height==len的后缀。
进一步分析,我们可以得到,若这个后缀可以覆盖在 k k k个后缀上,设字符集为 p p p那么最后的答案就是 C 26 p ∗ k C^p_{26}*k C26p∗k
接下来问题就变成了:如果我枚举覆盖在哪个后缀 i i i上,如何 n o w now now的覆盖判断是否合法?
解决这个问题还要考虑后缀树同构的必要条件:我们发现后缀树上会有一些分叉,也就是说,在这些分叉的地方必须要有不同的字符,进而我们可以通过原字符串的 h e i g h t height height,来限制每个位置的可选字符(就是哪些位置的字符不能一样)。
有了这些限制,我们对于每个后缀
i
i
i,看它和now的
L
C
P
LCP
LCP,若
L
C
P
<
l
e
n
n
o
w
LCP<len_{now}
LCP<lennow,那么显然不能覆盖。
否则的话我们暴力构出整个串(实际上不用构出),判断路径上的所有限制位置是否合法即可。
这里的复杂度证明我有点说不清楚,大概因为每个限制位置只会判断很少次,所以是合理的(至少我好像不会构造数据)。
(同学说是调和级数级别的,mengbi)
总的复杂度是后缀数组的 O ( n l o g n ) O(nlogn) O(nlogn),以及十分大的常数。
tip:这道题还可以用哈希解决。
大佬们如果看懂了题解欢迎指正qwq
官方题解
【参考代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
const int mod=42424242;
int n,T;
LL ans,bas;
int r[N],wa[N],wb[N],wx[N],wy[N];
int sa[N],rk[N],vis[N],height[N];
char st[N];
void init()
{
scanf("%s",st);n=strlen(st);
for(int i=0;i<=n+100;++i) r[i]=wa[i]=wb[i]=wx[i]=wy[i]=sa[i]=rk[i]=height[i]=0;
for(int i=0;i<=26;++i) vis[i]=0;
for(int i=0;i<n;++i) r[i]=st[i]-'a'+1;
//printf("!!!");for(int i=0;i<n;++i) printf("%d ",r[i]);puts("");
}
bool cmp(int *r,int a,int b,int l) {return r[a]==r[b] && r[a+l]==r[b+l];}
void da(int *r,int n,int m)
{
int *x=wa,*y=wb,*t,i,j,p;
for(i=0;i<m;++i) wx[i]=0;
for(i=0;i<n;++i) wx[x[i]=r[i]]++;
for(i=1;i<m;++i) wx[i]+=wx[i-1];
for(i=n-1;~i;--i) sa[--wx[x[i]]]=i;
for(j=1,p=0;p<n;j<<=1,m=p)
{
for(p=0,i=n-j;i<n;++i) y[p++]=i;
for(i=0;i<n;++i) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;++i) wy[i]=x[y[i]];
for(i=0;i<m;++i) wx[i]=0;
for(i=0;i<n;++i) wx[wy[i]]++;
for(i=1;i<m;++i) wx[i]+=wx[i-1];
for(i=n-1;~i;--i) sa[--wx[wy[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;++i)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void call_height(int *r,int n)
{
int i,j,k=0;
for(i=1;i<=n;++i) rk[sa[i]]=i;
for(i=0;i<n;height[rk[i++]]=k)
for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
}
void solve()
{
if(n==1) {puts("26");return;}
for(int i=0;i<n;++i) vis[r[i]]=1;
ans=1;bas=26;
for(int i=1;i<=26;++i)
if(vis[i]) (ans*=bas)%=mod,--bas;
da(r,n+1,256);
call_height(r,n);
//puts("!!");for(int i=1;i<=n;++i) printf("%d\n",height[i]);puts("!!!");
int brk=n+1;
for(int i=1;i<=n;++i)
if(max(height[i],height[i+1])==n-sa[i])
brk=min(brk,sa[i]);
if(brk==1) {puts("26");return;}
if(brk==n+1) {printf("%lld\n",ans);return;}
int cnt=rk[brk],len=n-brk,res=1;
int L=cnt,R=cnt,surep=brk-1;
for(int i=0;i<brk;++i)
{
int tmp=n+1,now=rk[i]-1;
while(now && sa[now]>=brk) tmp=min(tmp,height[++now]);
if(now) surep=max(surep,i+min(tmp,height[now+1])-1);
tmp=n+1;now=rk[i]+1;
while(now<=n && sa[now]>=brk) tmp=min(tmp,height[now++]);
if(now<=n) surep=max(surep,i+min(tmp,height[now])-1);
}
int surel=surep-brk+1;
int now=0,val=n+1;
for(int i=1;i<=n;++i)
{
val=min(val,height[i]);
if(sa[i]<=brk)
{
sa[++now]=sa[i];
if(i==cnt) cnt=now;
height[now]=val;val=n+1;
}
}
n=now;height[n+1]=0;
for(int i=1;i<=n;++i) rk[sa[i]]=i;
for(int i=cnt;i;--i)
{
if(height[i]<surel+1) {L=i-1;break;}
if(height[i]<len) ++res;
}
for(int i=cnt+1;i<=n+1;++i)
{
if(height[i]<surel+1) {R=i;break;}
if(height[i]<len) ++res;
}
while(L)
{
if(height[L+1]<surel) break;
int nexl=L-1;
for(int i=L;i;--i)
if(height[i]<surel+1) {nexl=i-1;break;}
bool flag=1;
for(int i=L;i>nexl;--i)
if(sa[i] && r[sa[i]-1]==r[brk-1]) {flag=0;break;}
if(!flag) {L=nexl;continue;}
++res;
for(int i=L;i>nexl+1;--i)
if(height[i]<len) ++res;
L=nexl;
}
while(R<=n)
{
if(height[R]<surel) break;
int nexr=R+1;
for(int i=R;i<=n;++i)
if(height[i+1]<surel+1) {nexr=i+1;break;}
bool flag=1;
for(int i=R;i<nexr;++i)
if(sa[i] && r[sa[i]-1]==r[brk-1]) {flag=0;break;}
if(!flag) {R=nexr;continue;}
++res;
for(int i=R;i<nexr-1;++i)
if(height[i+1]<len) ++res;
R=nexr;
}
printf("%lld\n",(LL)ans*res%mod);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CC_EST.in","r",stdin);
freopen("CC_EST.out","w",stdout);
#endif
scanf("%d",&T);
while(T--)
{
init();
solve();
}
return 0;
}
【总结】
观察性质,一顿乱搞,得到答案。