bzoj 2251: [2010Beijing Wc]外星联络 后缀数组

2251: [2010Beijing Wc]外星联络

Time Limit: 30 Sec  Memory Limit: 256 MB
Submit: 424  Solved: 232
[Submit][Status][Discuss]

Description

小 P 在看过电影《超时空接触》(Contact)之后被深深的打动,决心致力于寻
找外星人的事业。于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星
人发来的信息。虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高
低电平将接收到的信号改写为由 0 和 1 构成的串, 并坚信外星人的信息就隐藏在
其中。他认为,外星人发来的信息一定会在他接受到的 01 串中重复出现,所以
他希望找到他接受到的 01 串中所有重复出现次数大于 1 的子串。但是他收到的
信号串实在是太长了,于是,他希望你能编一个程序来帮助他。

Input

输入文件的第一行是一个整数N ,代表小 P 接收到的信号串的长度。
输入文件第二行包含一个长度为N 的 01 串,代表小 P 接收到的信号串。

Output

输出文件的每一行包含一个出现次数大于1 的子串所出现的次数。输出的顺
序按对应的子串的字典序排列。

Sample Input

7
1010101

Sample Output

3
3
2
2
4
3
3
2
2

HINT

对于 100%的数据,满足 0 <=? ? N     <=3000

  按理说以前写过的算法模板题就不该再写了,但是我后缀数组掌握的确实跟xiang一样,还是再写一遍。

  后缀数组需要将内存开大两倍,这个问题我就不赘述了。主要问题是求height数组,以前总觉得顺序问题很烦,其实也不难,只要搞清楚求height的转移顺序就行,一个位置的height求取就需要它在“字符串位置”中前一个位置的height值就行了,所以for语句应该一次枚举原数组的位置。

  剩下就比较简单了,我用O(n^2)的时间复杂度处理答案,不知道有没有更快的,一点小的注意事项,字典序排序注意起始位置相同的子串的顺序,其顺序与枚举顺序相反。也就是说我们在绕过一个坑的情况下防止跌进另一个坑中。

  最后hbw提到一个将随机字符串后缀数组优化到O(n)的方法,当rank数组最大值为n时直接break掉,貌似这是个简单有用的优化

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
#define MAXN 3010*2
char str[MAXN];
int sa[MAXN],tsa[MAXN];
int rank[MAXN],trank[MAXN];
int buc[MAXN];
int height[MAXN];
int theight[MAXN];
void IndexSort(int jp,int n)
{
        memset(buc,0,sizeof(buc));
        for (int i=0;i<n;i++)buc[rank[i+jp]]++;
        for (int i=1;i<=n;i++)buc[i]+=buc[i-1];
        for (int i=n-1;i>=0;i--)tsa[--buc[rank[i+jp]]]=i;
        memset(buc,0,sizeof(buc));
        for (int i=0;i<n;i++)buc[rank[tsa[i]]]++;
        for (int i=1;i<=n;i++)buc[i]+=buc[i-1];
        for (int i=n-1;i>=0;i--)sa[--buc[rank[tsa[i]]]]=tsa[i];
}
void SuffixArray(char* str,int n)
{
        for (int i=0;i<n;i++)trank[i]=str[i]-‘0‘+1;
        for (int i=0;i<n;i++)buc[trank[i]]++;
        for (int i=1;i<=n;i++)buc[i]+=buc[i-1];
        for (int i=n-1;i>=0;i--)sa[--buc[trank[i]]]=i;
        for (int i=0,x=0;i<n;i++)
        {
                if (!i || trank[sa[i]]!=trank[sa[i-1]])x++;
                rank[sa[i]]=x;
        }
        for (int j=1;j<n;j=j<<1)
        {
                IndexSort(j,n);
                int x=0;
                for (int i=0;i<n;i++)
                {
                        if (!i || rank[sa[i]]!=rank[sa[i-1]] || rank[sa[i]+j]!=rank[sa[i-1]+j])x++;
                        trank[sa[i]]=x;
                }
                for (int i=0;i<n;i++)rank[i]=trank[i];
                if (x==n)break;
        }
}
void InitHeight(int n)
{
        for (int i=0;i<n;i++)
        {
                if (rank[i]==1)continue;
                height[i]=max(height[i-1]-1,0);
                while (i+height[i]<n && sa[rank[i]-2]+height[i]<n
                                && str[i+height[i]]==str[sa[rank[i]-2]+height[i]])
                        height[i]++;
        }
        for (int i=1;i<n;i++)
                theight[i]=height[sa[i]];
}
vector<int> vec;
int stack[MAXN],tops=-1;
int main()
{
        freopen("input.txt","r",stdin);
        int n;
        int x;
        scanf("%d\n",&n);
        scanf("%s\n",str);
        SuffixArray(str,n);
        InitHeight(n);
    //    for (int i=0;i<n;i++)printf("%d ",sa[i]);printf("\n");
    //    for (int i=0;i<n;i++)printf("%s\n",str+sa[i]);printf("\n");
    //    for (int i=0;i<n;i++)printf("%d ",height[i]);printf("\n");
        for (int i=1;i<n;i++)
        {
                if (theight[i]<=theight[i-1])continue;
                x=i;
                for (int k=theight[i];k>theight[i-1];k--)
                {
                        while (x+1<n && theight[x+1]>=k)x++;
                        stack[++tops]=x-i+2;
                }
                while (~tops)
                        printf("%d\n",stack[tops--]);
        }
}
时间: 2024-08-02 02:49:21

bzoj 2251: [2010Beijing Wc]外星联络 后缀数组的相关文章

BZOJ 2251 2010Beijing WC 外星联络 后缀数组/Trie树

题目大意 给出一个字符串,问这个字符串中出现过1次以上的子串的个数,按照子串的字典序输出. 思路 由于数据范围过小,这个题有两个解法. 基本的想法就是用后缀数组来进行后缀的排序,之后按照height数组扫就可以了.应该是挺快的. 但是注意到数据范围只有3000,因此我们只需要弄出所有的后缀拿出来建立一颗后缀Trie树就行了.最后DFS一次树种的所有节点. CODE SuffixArray #include <cstdio> #include <cstring> #include &

BZOJ 2251: [2010Beijing Wc]外星联络

2251: [2010Beijing Wc]外星联络 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 795  Solved: 477[Submit][Status][Discuss] Description 小 P 在看过电影<超时空接触>(Contact)之后被深深的打动,决心致力于寻找外星人的事业.于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星人发来的信息.虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高低电平将接收到的信号

BZOJ 2251 [2010Beijing Wc]外星联络 trie的性质以及字符串性质

题意:链接 方法: 字典树 解析: zxr讲过的题,当时听好像就有一个什么神奇的结论,然后今天做的时候想了半天想起来是什么结论了, 对于一个字符串,它的所有后缀的所有前缀就能代表该串的所有子串. 然后呢,我们可以将所有的后缀都加到trie树里.end记录以某点为结尾的串出现多少次. 然后trie树之所以为字典树就是其从右往左走恰好是字典序,所以递归跑一遍输出满足题意的解. 另外,数组版需要开到n^2的空间复杂度,别问我为什么,不然你会各种RE,OLE,总会有神奇的数据将你卡住,所以最好老老实实开

【BZOJ-2251】外星联络 后缀数组 + 暴力

2251: [2010Beijing Wc]外星联络 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 670  Solved: 392[Submit][Status][Discuss] Description 小 P 在看过电影<超时空接触>(Contact)之后被深深的打动,决心致力于寻找外星人的事业.于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星人发来的信息.虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高低电平将接收到的信号

Bzoj2251 [2010Beijing Wc]外星联络

Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 867  Solved: 522 Description 小 P 在看过电影<超时空接触>(Contact)之后被深深的打动,决心致力于寻找外星人的事业.于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星人发来的信息.虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高低电平将接收到的信号改写为由 0 和 1 构成的串, 并坚信外星人的信息就隐藏在其中.他认为,外星人发来的信息一定会在他接受

【BZOJ2251】外星联络

2251: [2010Beijing Wc]外星联络 Time Limit: 30 Sec  Memory Limit: 256 MBSubmit: 870  Solved: 524[Submit][Status][Discuss] Description 小 P 在看过电影<超时空接触>(Contact)之后被深深的打动,决心致力于寻找外星人的事业.于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星人发来的信息.虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高低电平将接收到的信号

外星联络(bzoj 2251)

Description 小 P 在看过电影<超时空接触>(Contact)之后被深深的打动,决心致力于寻找外星人的事业.于是,他每天晚上都爬在屋顶上试图用自己的收音机收听外星人发来的信息.虽然他收听到的仅仅是一些噪声,但是他还是按照这些噪声的高低电平将接收到的信号改写为由 0 和 1 构成的串, 并坚信外星人的信息就隐藏在其中.他认为,外星人发来的信息一定会在他接受到的 01 串中重复出现,所以他希望找到他接受到的 01 串中所有重复出现次数大于 1 的子串.但是他收到的信号串实在是太长了,于

[BZOJ 1692] [Usaco2007 Dec] 队列变换 【后缀数组 + 贪心】

---恢复内容开始--- 题目链接:BZOJ - 1692 题目分析 首先,有个比较简单的贪心思路:如果当前剩余字符串的两端字母不同,就选取小的字母,这样显然是正确的. 然而若两端字母相同,我们怎么选取呢? 这时我们要从两端分别向内部比较,看那一端向内的字符串字典序小. 比如这个字符串 ABCDBA,从左端向内是 ABC.. 从右端向内是 ABD... 所以就选取左端的字符. 这样直接比较是 O(n^2) 的,我们可以使用后缀数组的 Rank 数组来比较. 我们在字符串后加上分隔符,然后再将字符

BZOJ 1031 JSOI 2007 字符加密Cipher 后缀数组

题目大意:给出一个字符串,循环字符串的起始位置,可以得到length个不同的字符串,问把这些字符串排序之后每一个字符串的第一位是什么. 思路:后缀数组裸题,只需要将整个字符串倍增,然后求一次sa. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 200010 using namespace std; char s[M