spoj 694 求一个字符串中不同子串的个数

SPOJ Problem Set (classical)

694. Distinct Substrings

Problem code: DISUBSTR

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. (转自:http://www.tuicool.com/articles/nMvEFvJ)

1、用前缀开看不同子串:可以看出-- 长度为i的字符串一共有i个前缀

2、每一个子串都是某个后缀的前缀,于是问题 等价于求所有不同的前缀的个数

然后按sa[1],sa[2]...逐次加入后缀观察: suffix(sa[i])长度为n-sa[i],一共有n-sa[i]个前缀,减去lcp[i-1](就是与前一个后缀的最长公共前缀的长度),就是新加入的新的前缀的个数

最后求和即可

注意我的lcp[i]指的是suffix(sa[i])和suffix(sa[i+1])的公共前缀的长度

#include <cstdio>
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstring>

using namespace std;
#define MAXN 1011

int n,k;//n=strlen(s);

int Rank[MAXN];
int tmp[MAXN];
char s[MAXN];
int lcp[MAXN],sa[MAXN];

/*使用Rank对sa排序*/
bool cmpSa(int i, int j)
{
  if(Rank[i] != Rank[j])return Rank[i] < Rank[j];
  else
  {   /*下面的Rank[t],已经是以t开头长度小于等于k/2的,
    sa[i]的名次,只是以i开头的后缀,而长度不同*/
    int ri = i+k <=n? Rank[i+k]:-1;
    int rj = j+k <= n ? Rank[j+k]:-1;
    return ri <rj;
  }
}

/*计算SA*/
void consa()
{
  /*n=strlen(s);  必要时注明*/
  /*初始化sarank保证两点
    1Rank[i]表示下标为i的是第几大,必须表示出相对大小,可以直接用字符代表其大小
    2sa[1...n]值为1..n*/
  for(int i=0;i<=n;i++){
    sa[i]=i;Rank[i] = i < n?s[i]:-1;
  }

  /*利用长度为k的字符串对长度为2*k的字符串排序*/
  for(k=1;k<=n;k*=2)/*注意此代码中k是全局变量 别乱用,循环必须从1开始,因为0*2=0*/
  {
    sort(sa,sa+n+1,cmpSa);
    tmp[sa[0]] = 0; /*此时tmp只是暂存rank*/
    for(int i=1;i<=n;i++){
      tmp[sa[i]] = tmp[sa[i-1]] +(cmpSa(sa[i-1],sa[i])?1:0);
      /*这一句很关键,等号右侧的sa[i]在此循环里表示第i大的长度小于等于k/2的字符串,
        从而求出第i大的长度小于等于k的字符串的sa[i]*/
    }
    for(int i=0;i<=n;i++){
      Rank[i] = tmp[i];
    }
  }
}

void construct_lcp()
{
  //n=strlen(s);
  for(int i=0; i<=n; i++)Rank[sa[i]]=i;

  int h=0;
  lcp[0]=0;
  for(int i=0;i<n;i++)
  {
    int j=sa[Rank[i]-1];

    if(h>0)h--;
    for(; j+h<n && i+h<n; h++)
    {
      if(s[j+h]!=s[i+h])break;
    }
    lcp[Rank[i]-1]=h;
  }
}

int main()
{
  int t,ans;
  scanf("%d",&t);
  while(t--)
  {
    ans=0;
    scanf("%s",s);
    n=strlen(s);
    consa();
    construct_lcp();
    for(int i=1;i<=n;i++)
    {
      ans+=n-sa[i]-lcp[i-1];
    }
    printf("%d\n",ans);
  }
  return 0;

  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值