算法分析
\(n\le 3000\),即\(n^2\)的时空复杂度都是可以接受的,我们考虑直接将所有的后缀插入一颗Trie
,那么这颗Trie
的所有前缀即为这个字符串的所有子串,在Trie
上跑前缀和即可统计每一个子串出现了多少次。因为Trie
的dfs遍历
就是按字典序排序后的结果,所以我们直接跑一边dfs
,如果某个子串出现次数\(\ge 2\),则输出这个字串。
我们换个想法:处理子串问题最常用的算法是什么?(我会SAM)后缀数组!
考虑任意两个后缀的前缀,这个前缀一定是1个出现过至少2次的子串,即我们题目中要求的东西。
下面问题就在于:如何不重不漏地把这样的子串求出来。
首先有一点不难证明:当\(i\)为一个定值,\(k\)为自变量,\(\forall k1,k2\in[i,n],k1<k2,LCP(i,k1)\ge LCP(i,k2)\)。
那么对于某一个后缀\(i\),设子串长度为\(j\),按排名枚举在\(i\)之后的每一个后缀(设当前枚举到的后缀为\(k\)),如果\(height(k)\ge j\),说明\(LCP(i,k)\ge j\),即该子串也在后缀\(k\)中出现。而且我们可以发现,对于每一个长度\(j\),如果比它更大的可行,那么它一定可行。所以我们可以从大到小枚举\(j\),为了防止出现重复统计,\(j\ge height(i)\),这样我们可以保证得到的子串一定是不重不漏的,并且按照这个顺序得到的答案恰好是与字典序相反的(因为有相同前缀的子串是按照长度从大到小的顺序枚举的)。
代码实现
#include<bits/stdc++.h>//Trie版
using namespace std;
#define maxn 3005
#define maxm 10000005
#define inf 0x3f3f3f3f
#define LL long long
#define mod 1000000007
#define local
template <typename Tp>
void read(Tp &x){
x=0;int fh=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
}
int n;
int a[maxn];
int ch[maxm][2],siz[maxm],cnt=1;
void ins(int id){
int p=1,dir;
for(int i=id;i<=n;i++){
dir=a[i];
if(!ch[p][dir])ch[p][dir]=++cnt;
p=ch[p][dir];siz[p]++;
}
}
void out(int p){
if(siz[p]>1)printf("%d\n",siz[p]);
if(ch[p][0])out(ch[p][0]);
if(ch[p][1])out(ch[p][1]);
}
int main(){
read(n);
for(int i=1;i<=n;i++)scanf("%1d",&a[i]);
for(int i=1;i<=n;i++)ins(i);
out(1);
return 0;
}