luogu 2408 不同子串个数 (后缀数组)

题目背景

因为NOI被虐傻了,蒟蒻的YJQ准备来学习一下字符串,于是它碰到了这样一道题:

题目描述

给你一个长为N的字符串,求不同的子串的个数

我们定义两个子串不同,当且仅当有这两个子串长度不一样 或者长度一样且有任意一位不一样。

子串的定义:原字符串中连续的一段字符组成的字符串

输入格式

第一行一个整数N

接下来一行N个字符表示给出的字符串

输出格式

一行一个整数,表示不一样的子串个数

输入输出样例

输入 #1

5
aabaa

输出 #1

11

输入 #2

3
aba

输出 #2

5

说明/提示

请使用64位整数来进行输出

(具体来说,C++和C选手请使用long long 类型,pascal选手请使用Int64)

由于输入文件过大,请使用 高效的读入方法(具体的,c++和c选手请不要使用cin,pascal选手不需要管)

对于30%的数据,N\le 1000N≤1000

对于100%的数据,N\le 10^5N≤105

题目链接:https://www.luogu.org/problem/P2408

题目分析:求出height,枚举排名,对排名为i的后缀,长度为n - sa[i] + 1,其中存在的不同的子串个数需要减去公共前缀,即h[i]

所以答案为∑(n - sa[i] + 1 - h[i])

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
int const MAX = 1e5 + 5;
char s[MAX];
int n, m, sa[MAX], height[MAX];
int rk[MAX], tp[MAX], tax[MAX];
 
bool cmp(int* r, int a, int b, int k) {
    return r[a] == r[b] && r[a + k] == r[b + k];
}
 
void radix_sort() {
    for (int i = 0; i <= m; i++) {
        tax[i] = 0;
    }
    for (int i = 1; i <= n; i++) {
        tax[rk[tp[i]]]++;
    }
    for (int i = 1; i <= m; i++) {
        tax[i] += tax[i - 1];
    }
    for (int i = n; i >= 1; i--) {
        sa[tax[rk[tp[i]]]--] = tp[i];
    }
}
 
void get_sa() {
    for (int i = 1; i <= n; i++) {
        rk[i] = s[i] - 'a' + 1;
        tp[i] = i;
        m = max(m, s[i] - 'a' + 1);
    }
    radix_sort();
    for (int j = 1, p = 0; p < n; j <<= 1, m = p) {
        p = 0;
        for (int i = n - j + 1; i <= n; i++) {
            tp[++p] = i;
        }
        for (int i = 1; i <= n; i++) {
            if (sa[i] > j) {
                tp[++p] = sa[i] - j;
            }
        }
        radix_sort();
        swap(tp, rk);
        rk[sa[1]] = p = 1;
        for (int i = 2; i <= n; i++) {
            rk[sa[i]] = cmp(tp, sa[i], sa[i - 1], j) ? p : ++p;
        }
    }
}
 
void get_height() {
    for (int i = 1, j = 0; i <= n; i++) {
        if (j) {
            j--;
        }
        int prevPos = sa[rk[i] - 1];
        while (i + j <= n && prevPos + j <= n && s[i + j] == s[prevPos + j]) {
            j++;
        }
        height[rk[i]] = j;
    }
}
 
int main() {
    scanf("%d %s", &n, s + 1);
    get_sa();
    get_height();
    // for (int i = 1; i <= n; i++) {
    //     printf("sa[%d] = %d rk[%d] = %d height[%d] = %d\n", i, sa[i], i, rk[i], i, height[i]);
    // }
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        int suffixLen = n - sa[i] + 1;
        ans += (ll) (suffixLen - height[i]);
    }
    printf("%lld\n", ans);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值