后缀数组学习与练习(入门向)

Zbq

算法概述:

首先后缀数组SA是后缀数组是后缀树的一个非常精巧的替代品,它比后缀树容易编程实现,能够实现后缀树的很多功能而时间复杂度也不太逊色,并且,它比后缀树所占用的空间小很多。可以说,在信息学竞赛中后缀数组比后缀树要更为实用。

在这之上主要学习了倍增算法来求后缀数组, 下面列举几个在学习中发现的困惑点。

  1. tmp数组储存的是第二关键字从小到大排序后的编码位置而不是编码,这点使我好久才弄明白代码的意义。
  2. 基数排序的最后一步反向来,保证在满足第一关键字的情况下,第二关键字的有序。
  3. 每次求出sa数组反向带入rank时,要有一个类似离散化的操作,老是把起点2写成1
  4. 求height数组是排名第i和I – 1的lcp,所以要还原sa进行比较

 

常见模型:

1.可重叠的最长重复子串问题

方法:求所有height的最大值就是答案

 

2.求不可重叠的最长重复子串问题

方法:首先二分length,转换成判定性问题,之后我们将所有height大于等于的划分成若干个段段,这样在每个段段里,两两之间都是lcp大于等于k的,如果其中有两个元素的sa值绝对值大于等于k那么就符合条件

 

3.可重叠的k次最长重复子串

方法:类似于上一个,我们要让只要有一个划分出来的段段的长度大于等于k – 1

 

4.求重复出现的子串数目

方法:可以考虑,每一个height值就能贡献当前值的答案,但是这样算下来,会有的子串算重复,考虑当前的和排名靠前一个的,如果当前的height大于前height的值,那么就多加上了前一个的height值,减去即可。

 

5.不相同子串计数问题

方法:和博客上写的不一样,这个题目我们可以先求出所有子串的个数,之后减去所有的height值,原因是每个子串必然是某个后缀的前缀。令S的长度为N,、则后缀SA[i]可以贡献出N-SA[i]个前缀。但其中有Height[i]个与之前的是重复的,减去之后正好能够得到答案。

 

6.字典序第k小字符串问题

方法:同上一个,我们在对不相同子串进行计数的时候,子串的字典序值是递增的,这样算到分界点然后二分或者枚举应该都可以

 

7.连续重复子串问题

方法:首先枚举子串的长度k,之后如果符合题目要求的话,suffix(1)和suffix(k + 1)的lcp应当是k + 1,这个就用些操作随便处理了,可以用st表, 也可以考虑两个后缀的sa必然相邻。

 

8.多个字符串的相关问题

方法:在本来的基础上,将字符串头尾相连,中间添加一个极大的字符,就可以进行一些常规操作了.

 

由于笔者蒟蒻  暂时就整理这些了

 

 

后缀数组练习     都是些板子题

1. P3809 【模板】后缀排序                                 求sa数组。。只是打个板子

2.  [JSOI2007]字符加密Cipher                            拆环为链 求sa数组

3. JZOJ 1598. 文件修复                                       求重复出现的子串数目

4. spoj694 Distinct Substrings                               统计本质不同的子串个数

5. poj1743:Musical Theme                                   差分,二分答案后缀数组

6.  [USACO06DEC]牛奶模式Milk Patterns          后缀数组 单调队列

7. [AHOI2013]差异                                                后缀数组 单调栈

8. [HAOI2016]找相同字符                                     后缀数组 单调栈

 

题目和解析

 

 1. P3809 【模板】后缀排序
题目背景
这是一道模板题。
题目描述
读入一个长度为 nn 的由大小写英文字母或数字组成的字符串,请把这个字符串的所有非空后缀按字典序从小到大排序,然后按顺序输出后缀的第一个字符在原串中的位置。位置编号为 11 到 nn 。
输入输出格式
输入格式:

一行一个长度为 nn 的仅包含大小写英文字母或数字的字符串。
输出格式:

一行,共n个整数,表示答案。
方法:裸的求sa啊 -。-。-。-
代码: 
#include<cstdio>
#include<algorithm>
#include<cstring>
#define M 1000010
using namespace std;
char s[M];
int tex[M], num[M], rank[M], sa[M], tmp[M], n, m, q;

void qsort()
{
    for(int i = 0; i <= m; i++) tex[i] = 0;
    for(int i = 1; i <= n; i++) tex[rank[tmp[i]]]++;
    for(int i = 1; i <= m; i++) tex[i] += tex[i - 1];
    for(int i = n; i >= 1; i--) sa[tex[rank[tmp[i]]]--] = tmp[i];
}

bool check(int l, int r, int wei)
{
    return tmp[l] == tmp[r] && tmp[l + wei] == tmp[r + wei];
}

void suffix()
{
    for(int i = 1; i <= n; i++) rank[i] = num[i], tmp[i] = i;
    m = 127; qsort();
    for(int w = 1; q < n; w <<= 1, m = q)
    {
        q = 0;
        for(int i = n - w + 1; i <= n; i++) tmp[++q] = i;
        for(int i = 1; i <= n; i++) if(sa[i] > w) tmp[++q] = sa[i] - w;
        qsort();
        swap(rank, tmp);
        q = rank[sa[1]] = 1;
        for(int i = 2; i <= n; i++) if(check(sa[i], sa[i - 1], w)) rank[sa[i]] = q; else rank[sa[i]] = ++q;
    }
}

int main()
{
    scanf("%s", s);
    n = strlen(s);
    for(int i = 1; i <= n; i++) num[i] = s[i - 1];
    suffix();
    for(int i = 1; i <= n; i++) printf("%d ", sa[i]);
    return 0;
}
View Code

 

***********************************************************************************

2.  [JSOI2007]字符加密Cipher

Time Limit: 10 Sec Memory Limit: 162 MB 
Submit: 4175 Solved: 1694 
[Submit][Status][Discuss] 
Description

喜欢钻研问题的JS 同学,最近又迷上了对加密方法的思考。一天,他突然想出了一种他认为是终极的加密办法:把需要加密的信息排成一圈,显然,它们有很多种不同的读法。例如下图,可以读作:

 

JSOI07 SOI07J OI07JS I07JSO 07JSOI 7JSOI0 把它们按照字符串的大小排序: 07JSOI 7JSOI0 I07JSO JSOI07 OI07JS SOI07J 读出最后一列字符:I0O7SJ,就是加密后的字符串(其实这个加密手段实在很容易破解,鉴于这是突然想出来的,那就^^)。但是,如果想加密的字符串实在太长,你能写一个程序完成这个任务吗?

Input

输入文件包含一行,欲加密的字符串。注意字符串的内容不一定是字母、数字,也可以是符号等。

Output

输出一行,为加密后的字符串。

方法:

安环状来读可以考虑我们以前用的一个算法叫  拆环为链    之后要我们求的是sa数组  注意输出的时候要考虑位置只能是前n个

代码:

 

#include<cstdio>

#include<algorithm>

#include<cstring>

#include<iostream>

#define M 200020

using namespace std;

int tex[M], num[M], rank[M], sa[M], tmp[M], n, m, q;

char s[M];

 

void qsort() {

    for(int i = 0; i <= m; i++) tex[i] = 0;

    for(int i = 1; i <= n; i++) tex[rank[tmp[i]]]++;

    for(int i = 1; i <= m; i++) tex[i] += tex[i - 1];

    for(int i = n; i >= 1; i--) sa[tex[rank[tmp[i]]]--] = tmp[i];

}

 

bool cmp(int l, int r, int wei) {

    return tmp[l] == tmp[r] && tmp[l + wei] == tmp[r + wei];

}

 

void suffix() {

    for(int i = 1; i <= n; i++) rank[i] = num[i], tmp[i] = i;

    m = 127;

    qsort();

    for(int w = 1; q < n; w <<= 1, m = q) {

        q = 0;

        for(int i = n - w + 1; i <= n; i++) tmp[++q] = i;

        for(int i = 1; i <= n; i++) if(sa[i] > w) tmp[++q] = sa[i] - w;

        qsort();

        swap(tmp, rank);

        rank[sa[1]] = q = 1;

        for(int i = 2; i <= n; i++) if(cmp(sa[i], sa[i - 1], w)) rank[sa[i]] = q;

            else rank[sa[i]] = ++q;

    }

}

 

int main() {

    scanf("%s", s);

    n = strlen(s);

    for(int i = 1; i <= n; i++) num[i] = num[i + n] = s[i - 1], s[i + n - 1] = s[i - 1];

    n += n;

    suffix();

    for(int i = 1; i <= n; i++) {

        if(sa[i] <= (n >> 1)) putchar(s[sa[i] + (n >> 1) - 2]);

    }

    return 0;

}
View Code

相关文章: