2251: [2010Beijing Wc]外星联络
Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 870 Solved: 524
[Submit][Status][Discuss]
Description
小 P 在看过电影《超时空接触》(Contact)之后被深深的打动,决心致力于寻
找外星人的事业。于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星
人发来的信息。虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高
低电平将接收到的信号改写为由 0 和 1 构成的串, 并坚信外星人的信息就隐藏在
其中。他认为,外星人发来的信息一定会在他接受到的 01 串中重复出现,所以
他希望找到他接受到的 01 串中所有重复出现次数大于 1 的子串。但是他收到的
信号串实在是太长了,于是,他希望你能编一个程序来帮助他。
Input
输入文件的第一行是一个整数N ,代表小 P 接收到的信号串的长度。
输入文件第二行包含一个长度为N 的 01 串,代表小 P 接收到的信号串。
Output
输出文件的每一行包含一个出现次数大于1 的子串所出现的次数。输出的顺
序按对应的子串的字典序排列。
Sample Input
1010101
Sample Output
3
2
2
4
3
3
2
2
HINT
对于 100%的数据,满足 0 <= N <=3000
Source
/*In Search Of Life*/ #include<cstdio> #include<cstdlib> #include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<iomanip> #include<stack> #include<map> #include<set> #include<cmath> #define debug(x) cerr<<#x<<"="<<x<<endl #define INF 0x7f7f7f7f #define llINF 0x7fffffffffffll using namespace std; typedef pair<int,int> pii; typedef long long ll; inline int init() { int now=0,ju=1;char c;bool flag=false; while(1) { c=getchar(); if(c=='-')ju=-1; else if(c>='0'&&c<='9') { now=now*10+c-'0'; flag=true; } else if(flag)return now*ju; } } inline long long llinit() { long long now=0,ju=1;char c;bool flag=false; while(1) { c=getchar(); if(c=='-')ju=-1; else if(c>='0'&&c<='9') { now=now*10+c-'0'; flag=true; } else if(flag)return now*ju; } } int n,m,auxa[10005],auxsort[10005],auxb[10005],auxval[10005],sa[10005],height[10005],rank[10005]; char str[10005]; int ans[5000505]; int c[10005]; int cnt=0; void getsa() { int *x=auxa,*y=auxb,t,cnt=0;m=256; for(int i=1;i<=n;i++)auxsort[x[i]=str[i]]++; for(int i=2;i<=m;i++)auxsort[i]+=auxsort[i-1]; for(int i=n;i>=1;i--)sa[auxsort[x[i]]--]=i; for(int j=1;cnt<n;j<<=1,m=cnt) { cnt=0; for(int i=n-j+1;i<=n;i++)y[++cnt]=i; for(int i=1;i<=n;i++)if(sa[i]-j>0)y[++cnt]=sa[i]-j; for(int i=1;i<=n;i++)auxval[i]=x[y[i]]; for(int i=0;i<=m;i++)auxsort[i]=0; for(int i=1;i<=n;i++)++auxsort[auxval[i]]; for(int i=2;i<=m;i++)auxsort[i]+=auxsort[i-1]; for(int i=n;i>=1;i--)sa[auxsort[auxval[i]]--]=y[i]; swap(x,y);cnt=x[sa[1]]=1; for(int i=2;i<=n;i++) { if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j])x[sa[i]]=cnt; else x[sa[i]]=++cnt; } } for(int i=1;i<=n;i++)rank[sa[i]]=i; cnt=0; for(int i=1;i<=n;i++) { if(rank[i]==1)continue; if(cnt)cnt--; int j=sa[rank[i]-1]; while(str[i+cnt]==str[j+cnt])++cnt; height[rank[i]]=cnt; } return; } int main() { n=init();scanf("%s",str+1); getsa(); for(int i=n;i>=1;i--) { for(int j=1;j<=height[i+1];j++) { c[j]++; } for(int j=n;j>=height[i+1]+1;j--) { if(c[j]>1)ans[++cnt]=c[j]; c[j]=1; } } for(int j=n;j>=1;j--) { if(c[j]>1)ans[++cnt]=c[j]; } for(int i=cnt;i>=1;i--)printf("%d ",ans[i]); return 0; }
感谢涵哥指导
跑了160ms 我觉得还是挺快的
涵哥@goodqt 说这题最快可以$O(min(字符集大小log字符集大小,n^{2})+n^{2})$
具体做法是这样
我们考虑从后往前加入每个字符串
比如说
5
10101
这样求出来rank是
01 0
0101 2
1 0
101 1
10101 3
然后我们从后往前扫,记录数组ans[]
每次我们将ans从1到n置为1
枚举i从n到1 将1-height[i+1]++ height[i+1]-n扫一下看看有没有超过1的数 有就全部弹出到一个栈内
然后这样最后全部弹掉 然后我们从上往下把所有元素输出即可
证明:这样一定满足字典序且不重不漏
每个子串只会在它应该被统计的时候统计上 且我们从后往前枚举 一定保证了字典序大的数最先被压进去。
证明:这样一定能统计出答案
每个重复子串只会在lcp处被计算 那么显然最后所有子串都会被统计,得证。
UPD:
我发现这是我最近做的SA题 写点东西提醒一下自己
假如遇到bzoj1717这种题 可以考虑全程用getVal()比较 而不是直接比较是否相等(会出现全等于0的情况)
以及最大的问题 不要别人说啥就是啥 自己试一试!