欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【后缀数组 不同的字串个数】SPOJ - SUBST1 New Distinct Substrings

程序员文章站 2022-04-30 20:37:12
...

Problem Description

给你一个串,问你这个串有多少个不同的字串

思路:

后缀数组学习过程:wwl巨巨讲解了一波,然后参考博客,09年集训队论文后缀数组–处理字符串的有力工具。
【后缀数组 不同的字串个数】SPOJ - SUBST1 New Distinct Substrings
后缀串,按字典序从小到大排序。求两两之间的公共字串长度,就是height[]数组。
height[i]:后缀串,按字典序从小到大排序,第 i 后缀串 和 第 i-1 后缀串的最长公共前缀;
知道 height[] 数组后,我们就知道任意两个后缀串的最长公共前缀 = 它们之间 height[] 数组的最小值。例如上图:aaab 和 abaaaab 的最长公共前缀 = 之间的最小值 1。
证明:a 和 b 的 LCP = 3,b 和 c 的 LCP = 4,所以 a 和 c 的 LCP = 3,用到这个理论。
思路:这题是求不同子串的个数,可以转变为两种方式:
1:每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀不同前缀的个数。
后缀串,按字典序从小到大的顺序,对于每次新加进来第 k 小的后缀串,它将产生 n - sa[k] - 1 个前缀,但是有 height[k] 个是和前面的前缀一样的。所以新加进来的后缀串贡献了:n - sa[k] - 1 + height[k] 个不同的子串。累加后就是原问题答案。
2:每个子串一定是某个后缀的前缀,那么原问题等价于求 总的子串个数 - 所有后缀相同前缀的个数。
总的子串个数:n*(n+1)/2
所有后缀相同前缀的个数:对于新加进来的后缀串,只有 height[i] 个前缀是相同的,所以结果为 height[] 的累加和。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 200010;
int cntA[maxn], cntB[maxn], sa[maxn], tsa[maxn], A[maxn], B[maxn], height[maxn];
int Rank[maxn];
long long n;//串的长度的平方会超过int
char ch[maxn];
//sa[i]代表第i小的后缀位置,Rank[i]代表第i位置的后缀,排名第几小
void solve()//就是为了求sa[],Rank[],height[]数组。sa[]和Rank[]数组是互逆的,所以知道其中一个另外一个就可以O(n)复杂度求出来
{
    for(int i = 0; i < 256; i++) cntA[i] = 0;
    for(int i = 1; i <= n; i++) cntA[ch[i-1]]++;
    for(int i = 1; i < 256; i++) cntA[i] += cntA[i-1];
    for(int i = n; i; i--) sa[cntA[ch[i-1]]--] = i;
    //上面为了求sa[]数组
    Rank[sa[1]] = 1;
    for(int i = 2; i <= n; i++)
    {
        Rank[sa[i]] = Rank[sa[i-1]];
        if(ch[sa[i]-1] != ch[sa[i-1]-1]) Rank[sa[i]]++;
    }
    //上面的那么多代码,就是为了求后缀长度为1的Rank[]数组
    for(int l = 1; Rank[sa[n]] < n; l <<= 1)//倍增后缀长度求Rank[]数组,长度不够补0
    {
        memset(cntA, 0, sizeof(cntA));
        memset(cntB, 0, sizeof(cntB));
        for(int i = 1; i <= n; i++)
        {
            cntA[A[i] = Rank[i]]++;
            cntB[B[i] = (i+l <= n)?Rank[i+l]:0]++;
        }
        for(int i = 1; i <= n; i++) cntB[i] += cntB[i-1];
        for(int i = n; i; i--) tsa[cntB[B[i]]--] = i;
        for(int i = 1; i <= n; i++) cntA[i] += cntA[i-1];
        for(int i = n; i; i--) sa[cntA[A[tsa[i]]]--] = tsa[i];
        //上面为了求sa[]数组。i是第一类,i+l是第二类,tsa[i]代表第二类第i小的后缀位置
        Rank[sa[1]]=1;
        for(int i = 2; i <= n; i++)
        {
            Rank[sa[i]] = Rank[sa[i-1]];
            if(A[sa[i]] != A[sa[i-1]] || B[sa[i]] != B[sa[i-1]]) Rank[sa[i]]++;
        }
        //求Rank[]数组
    }
    for(int i = 1, j = 0; i <= n; i++)//求height[]数组
    {
        if(j) j--;
        while(ch[i+j-1] == ch[sa[Rank[i]-1] + j - 1]) j++;
        height[Rank[i]] = j;
    }
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%s", ch);
        n = strlen(ch);
        if(n == 1)
        {
            printf("1\n");
            continue;
        }
        solve();
        long long ans = (n+1)*n/2;
        for(int i = 2; i <= n; i++) ans -= height[i];//减去所有重复的字串
        printf("%lld\n", ans);
    }
}