BZOJ 3230 相似子串 后缀数组

来源:互联网 发布:人工智能有哪些岗位 编辑:程序博客网 时间:2024/06/06 07:17

之前觉得后缀数组谜一样的豆腐块代码很难理解,于是先去学了后缀自动机,然而这道题让我发现后缀数组还是很好用的工具QaQ。

关于这道题的每个询问首先要求出第i小和第j小的子串在原串的位置,然后快速地求出这两个子串的最长公共前缀和后缀。

第一个步骤我们可以用二分搞定,通过原串的height数组我们可以求出一个sum数组,sum[i]表示按照字典序排序后,前i个后缀中共有多少个本质不同的子串。在n个后缀中,每个子串都可以被表示为某个后缀的前缀,所以我们在sum数组中二分查找出第一个不小于k的sum[i]来求原串第k小的子串。那么第i个后缀的某个前缀就是所需的子串了,这个稍微计算一下得出。

我们得出了两个子串[la,ra]和[lb,rb],要求它们的最长公共前缀以及后缀,最长公共前缀就是height数组上的区间最小值问题了,那么后缀我们按照原串的反串建立后缀数组也就变成了height数组上的区间最小值问题了。

#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>#define SZ 100005using namespace std;int n, q, s1[SZ], s2[SZ];int sa1[SZ], r1[SZ], h1[SZ], rmq1[SZ][18];int sa2[SZ], r2[SZ], h2[SZ], rmq2[SZ][18];long long sum[SZ];void Read (){    scanf ("%d %d", &n, &q);    char c = getchar();    while (c < 'a' || c > 'z') c = getchar();    for (int i = 1; i <= n; i++)    {        s1[i-1] = s2[n-i] = c-'a'+1;        c = getchar();    }}int cmp (int *s, int i, int j, int l){return s[i]==s[j] && s[i+l]==s[j+l];}void buildsa (int *s, int *sa, int *rank, int *height, int m) {    n++;    int a[SZ], b[SZ], c[SZ], d[SZ];    int i, j, p, k = 0, *x = a, *y = b, *t;    for (i = 0; i < m; i++) c[i] = 0;    for (i = 0; i < n; i++) c[x[i]=s[i]]++;    for (i = 1; i < m; i++) c[i] += c[i-1];    for (i = n-1; i >= 0; i--) sa[--c[x[i]]] = i;        for (j = p = 1; p < n; j<<=1, m=p)    {        for (p = 0, i = n-j; i < n; i++) y[p++] = i;        for (i = 0; i < n; i++)             if (sa[i] >= j) y[p++] = sa[i]-j;        for (i = 0; i < n; i++) d[i] = x[y[i]];        for (i = 0; i < m; i++) c[i] = 0;        for (i = 0; i < n; i++) c[d[i]]++;        for (i = 1; i < m; i++) c[i] += c[i-1];        for (i = n-1; i >= 0; i--) sa[--c[d[i]]] = y[i];        for (t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1; i < n; i++)        x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p-1 : p++;    }    n--;    for (i = 1; i <= n; i++) rank[sa[i]] = i;    for (i = 0; i <  n; height[rank[i++]] = k)    for (k?k--:0, j=sa[rank[i]-1]; s[i+k]==s[j+k]; k++);}void BuildSa (){    buildsa(s1,sa1,r1,h1,27);    buildsa(s2,sa2,r2,h2,27);    sum[1] = n-sa1[1];    for (int i = 2; i <= n; i++)        sum[i] = sum[i-1]+(long long)(n-sa1[i]-h1[i]);}void BuildRmq (){    for (int i = 1; i <= n; i++)        rmq1[i][0] = h1[i], rmq2[i][0] = h2[i];    for (int i = 1; (1<<i) <= n; i++)    for (int j = 1; j+(1<<i)-1 <= n; j++)        rmq1[j][i] = min(rmq1[j][i-1],rmq1[j+(1<<i-1)][i-1]),        rmq2[j][i] = min(rmq2[j][i-1],rmq2[j+(1<<i-1)][i-1]);}void Query (long long k, int &lk, int &rk){    if (sum[n] < k) {lk = -1; return;}    int l = 1, r = n, mid;    while (l < r)    {        mid = (l+r)/2;        if (sum[mid] >= k) r = mid;        else l = mid + 1;    }    lk = sa1[l];    rk = sa1[l]+k+h1[l]-(l?sum[l-1]:0)-1;}int lcp (int l, int r, bool flag){    if (l == r) return 9999999;    if (flag)    {        l = r1[l], r= r1[r];        if (l > r) swap(l,r);        int k = 0; l++;        if (l == r) return rmq1[l][0];        while ((1<<k+1) <= r-l+1) k++;        return min(rmq1[l][k],rmq1[r-(1<<k)+1][k]);    }    else    {        l = r2[l], r= r2[r];        if (l > r) swap(l,r);        int k = 0; l++;        if (l == r) return rmq2[l][0];        while ((1<<k+1) <= r-l+1) k++;        return min(rmq2[l][k],rmq2[r-(1<<k)+1][k]);    }}void SolveQue (){    #define SKIP_THIS puts("-1");continue;    long long a, b, ansa, ansb, mxans;    int la, lb, ra, rb;    while (q--)    {        scanf ("%lld %lld", &a, &b);        Query (a, la, ra);        Query (b, lb, rb);        if (la < 0 || lb < 0) {SKIP_THIS}        mxans = (long long)(min(ra-la,rb-lb) + 1);        ansa = min((long long)lcp(la,lb,1), mxans);        ansb = min((long long)lcp(n-ra-1,n-rb-1,0), mxans);        printf ("%lld\n", ansa*ansa+ansb*ansb);    }}int main (){    Read();    BuildSa();    BuildRmq();    SolveQue();    return 0;}

【蒟蒻的自述】
然而蒟蒻不理解后缀数组的豆腐块代码,所以一开始出了好多错误。
在前一部分计算sa数组的时候,为了防止出错所以在原串后加入一个原串没有的字符,并且它小于任何一个原串内的字符,所以传入的n实际是原串长度+1,并且原串s转换后最小字符不能是0,因为被加在末尾的字符是最小的。这也导致sa数组的有效位置是[1,n],而rank仍然是[0,n-1],不过我们仍然可以按照字面意思来理解它们,sa[i]表示排名第i的后缀的开始位置,rank[i]表示开始位置为i的后缀的排名,height[i]表示排名第i的后缀与排名第i-1的后缀的LCP。

0 0
原创粉丝点击