ZigZagKmp
Think twice, code once.

题目传送门

算法分析

\(n\le 3000\),即\(n^2\)的时空复杂度都是可以接受的,我们考虑直接将所有的后缀插入一颗Trie,那么这颗Trie的所有前缀即为这个字符串的所有子串,在Trie上跑前缀和即可统计每一个子串出现了多少次。因为Triedfs遍历就是按字典序排序后的结果,所以我们直接跑一边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;
}

后缀数组版(找了一篇题解)

posted on 2019-09-08 17:50  ZigZagKmp  阅读(133)  评论(0编辑  收藏  举报