C - Distinct Substrings SPOJ - DISUBSTR (后缀数组)

本文介绍了一种计算字符串中所有不同连续子序列数量的方法。利用高度数组和字典序排列来减少重复计数,通过具体示例解释了算法背后的逻辑,并提供了完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目意思是求给定一序列中所有不同连续子序列的个数。
对于一个长度为n的序列那么其总共的连续子序列的个数是(n+1)*n/2;所以只要用总共的减去重复的就可以了。如何计算重复的?还是要用到height数组,由于字典序排好的后缀,所以如果当前相邻字符串的公共前缀长度为k的话,那么只要用总数减取k就行了,比如一个串为abcabc在字典序中,adc与adcadc是相邻的,所以就说明了有长度为3的共同部分,所以要减去3.
我在这里想了一会,既然公共的前缀为abc为什么只减去3,明明abc代表了,6个子串,为什么不减去(k+1)*k/2。最后想明白了,这个3减去的是以a开头的子串,而相bc这样的会在 bc与bcabc这组的height中被处理掉,所以这样做刚刚好除去了所有的,且不重复。

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define Max_N (2000 + 100)
int n;
int k;
int a[Max_N];
int rank1[Max_N];
int tmp[Max_N];
bool compare_sa(int i, int j)
{
    if(rank1[i] != rank1[j]) return rank1[i] < rank1[j];
    else {
        int ri = i + k <= n ? rank1[i + k] : -1;
        int rj = j + k <= n ? rank1[j + k] : -1;
        return ri < rj;
    }
}

void construct_sa(int buf[], int s, int sa[])
{
    int len = s;
    for (int i = 0; i <= len; i++) {
        sa[i] = i;
        rank1[i] = i < len ? buf[i] : -1;
    }

    for ( k = 1; k <= len; k *= 2) {
        sort(sa, sa + len +1, compare_sa);
        tmp[sa[0]] = 0;
        for (int i = 1; i <= len; i++) {
            tmp[sa[i]] = tmp[sa[i-1]] + (compare_sa(sa[i-1], sa[i]) ? 1 : 0);
        }
        for (int i = 0; i <= len; i++) {
            rank1[i] = tmp[i];
        }
    }
}

void construct_lcp(int buf[], int len, int *sa, int *lcp)
{
    int h = 0;
    lcp[0] = 0;
    for (int i = 0; i < len; i++) {
        int j = sa[rank1[i] - 1];
        if (h > 0) h--;
        for (; j + h < len && i + h < len; h++) {
            if (buf[j+h] != buf[i+h]) break;
        }
        lcp[rank1[i] - 1] = h;
    }
}
int sa[Max_N];
int rev[Max_N];
int lcp[Max_N];
char buf1[Max_N];
int main()
{ 
    int T;
    cin >> T;
    while (T--) {
        scanf ("%s", buf1);
        n = strlen(buf1);
        for (int i = 0; i < n; i++)
            a[i] = int(buf1[i]);
        construct_sa(a, n, sa);
        construct_lcp(a, n, sa, lcp);
        int ans = (n + 1) * n / 2;
        for (int i = 0; i < n; i++)
            ans -= lcp[i];
        printf("%d\n", ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值