spoj694,spoj705 后缀数组/不相同的子串的个数 计算贡献


Given a string, we need to find the total number of its distinct substrings.

Input

T- number of test cases. T<=20;
Each test case consists of one string, whose length is <= 1000

Output

For each test case output one number saying the number of distinct substrings.

Example

Sample Input:
2
CCCCC
ABABA

Sample Output:
5
9

Explanation for the testcase with string ABABA: 
len=1 : A,B
len=2 : AB,BA
len=3 : ABA,BAB
len=4 : ABAB,BABA
len=5 : ABABA
Thus, total number of distinct substrings is 9.





每个子串一定是某个后缀的前缀,那么原问题等价于求所有后缀之间的不相
同 的 前 缀 的 个 数 。 如 果 所 有 的 后 缀 按 照 suffix(sa[1]), suffix(sa[2]),
suffix(sa[3]), ...... ,suffix(sa[n])的顺序计算,不难发现,对于每一次新加
进来的 后缀 suffix(sa[k]), 它将产生 n-sa[k]+1 个新 的前缀。 但是其中有
height[k]个是和前面的字符串的前缀是相同的。所以 suffix(sa[k])将“贡献”
出 n-sa[k]+1- height[k]个不同的子串。累加后便是原问题的答案。这个做法
的时间复杂度为 O(n)。



#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100005;
struct SuffixArray {
  char s[maxn];      // 原始字符数组(最后一个字符应必须是0,而前面的字符必须非0)
  int sa[maxn];     // 后缀数组
  int rank1[maxn];   // 名次数组. rank[0]一定是n-1,即最后一个字符
  int height[maxn]; // height数组
  int t[maxn], t2[maxn], c[maxn]; // 辅助数组
  int n; // 字符个数

  void clear() { n = 0; memset(sa, 0, sizeof(sa)); }

  // m为最大字符值加1。调用之前需设置好s和n
  void build_sa(int m) {
    int i, *x = t, *y = t2;
    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(int k = 1; k <= n; k <<= 1) {
      int p = 0;
      for(i = n-k; i < n; i++) y[p++] = i;
      for(i = 0; i < n; i++) if(sa[i] >= k) y[p++] = sa[i]-k;
      for(i = 0; i < m; i++) c[i] = 0;
      for(i = 0; i < n; i++) c[x[y[i]]]++;
      for(i = 0; i < m; i++) c[i] += c[i-1];
      for(i = n-1; i >= 0; i--) sa[--c[x[y[i]]]] = y[i];
      swap(x, y);
      p = 1; x[sa[0]] = 0;
      for(i = 1; i < n; i++)
        x[sa[i]] = y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k] ? p-1 : p++;
      if(p >= n) break;
      m = p;
    }
  }

  void build_height() {
    int i, j, k = 0;
    for(i = 0; i < n; i++) rank1[sa[i]] = i;
    for(i = 0; i < n; i++) {
      if(k) k--;
      int j = sa[rank1[i]-1];
      while(s[i+k] == s[j+k]) k++;
      height[rank1[i]] = k;
    }
  }
};

SuffixArray sol;

int T;
int main()
{
//    freopen("data.txt","r",stdin);
    ios_base::sync_with_stdio(false);
    cin >> T;
    while(T--)
    {
        memset(sol.s,0,sizeof(sol.s));
        cin >> sol.s;
        sol.clear();
        sol.n = strlen(sol.s)+1;
        sol.build_sa(130);
//        for(int i=0;i<sol.n;i++)
//        {
//            cout << sol.sa[i]<<" ";
//        }
//        cout <<endl;
        sol.build_height();
//        sol.n--;
        ll ans = 0 ;

        for(int i=1;i<sol.n;i++)
        {
            ans += sol.n - sol.sa[i]-1 - sol.height[i];
        }
        cout << ans<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值