HDU 6194 string string string (2017沈阳网赛-后缀数组)

本文介绍了一种利用后缀数组解决字符串中不同子串恰好出现k次的问题的方法。通过枚举后缀和计算最长公共前缀,有效地找出满足条件的子串数量。

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

题意:

告诉你一个字符串和k , 求这个字符串中有多少不同的子串恰好出现了k 次。

思路:

后缀数组。

我们先考虑至少出现k 次的子串, 所以我们枚举排好序的后缀i (sa[i]) 。

k段k 段的枚举。

假设当前枚举的是 sa[i]~sa[i + k -1]

那么假设这一段的最长公共前缀  是L 的话。

那么就有L 个不同的子串至少出现了k次。

我们要减去至少出现k + 1次的 , 但还要和这个k 段的lcp 有关系, 因此肯定就是 这一段 向上找一个后缀 或者向下找一个后缀。

即  sa[i-1] ~ sa[i + k - 1]  和 sa[i] ~ sa[i + k] 求两次lcp 减去即可。

但是会减多了。

减多的显然是sa[i-1] ~ sa[i + k] 的lcp。 加上即可。


注意 k =1的情况在求lcp 会有 问题, 即求一个串的最长公共前缀会有问题, 特判一下即可。

一定要注意边界问题 边界问题 边界问题!!!

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 100000 + 10;

int t1[maxn], t2[maxn], c[maxn];

bool cmp(int* r, int a,int b,int l){
    return r[a] == r[b] && r[a+l] == r[b+l];
}

void da(int str[], int sa[], int Rank[], int lcp[], int n, int m){
    ++n;
    int i, j, p, *x = t1, *y = t2;
    for (i = 0; i < m; ++i) c[i] = 0;
//        puts("hha");
    for (i = 0; i < n; ++i) c[x[i] = str[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 (j = 1; j <= n; j <<= 1){
        p = 0;
        for (i = n-j; i < n; ++i) y[p++] = i;
        for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j;
        for (i = 0; i < m; ++i) c[i] = 0;
        for (i = 0; i < n; ++i) c[x[y[i] ] ]++;

        for (i = 1; 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] ] = cmp(y, sa[i-1], sa[i], j) ? p-1 : p++;


        }

        if (p >= n)break;
        m= p;


    }

    int k = 0;
    n--;
    for (i = 0; i <= n; ++i) Rank[sa[i] ] = i;
    for (i = 0; i < n; ++i){
        if (k)--k;
        j = sa[Rank[i]-1 ];
        while(str[i+k] == str[j+k])++k;
        lcp[Rank[i] ] = k;
    }
}

int lcp[maxn], a[maxn], sa[maxn], Rank[maxn];

char s[maxn];

int d[maxn][40];
int len;

void rmq_init(int* A, int n){
    for (int i = 0; i < n; ++i) d[i][0] = A[i];
    for (int j = 1; (1<<j) <= n; ++j)
        for (int i = 0; i + (1<<j) - 1 < n ; ++i)
            d[i][j] = min(d[i][j-1], d[i + (1<< (j-1))][j-1]);
}

int ASK(int l,int r){
    int k = 0;
    while((1<<(k+1)) <= r-l + 1)++k;
    return min(d[l][k], d[r-(1<<k) + 1][k]);
}

int ask(int l,int r){
    if (l == r) return len - sa[r]; /// l == r的话 是一个串, 返回本身的长度即可。
    return ASK(l + 1, r); ///否则在rmq查询。
}

//
int main(){
    int T;
    scanf("%d", &T);

    while(T--){
        int k;
        scanf("%d", &k);
        scanf("%s", s);
        len = strlen(s);
        for (int i = 0; i < len; ++i){
            a[i] = s[i] - 'a' + 1;
        }
        a[len] = 0;
        da(a, sa, Rank, lcp, len, 30);
        rmq_init(lcp, len + 1);
        long long ans = 0;
        for (int i = 1; i + k - 1 <= len; ++i){
            ans += ask(i, i + k - 1);
            if (i - 1 > 0)ans -= ask(i - 1, i + k - 1); ///注意边界问题。
            if (i + k <= len)ans -= ask(i, i + k);
            if (i - 1 > 0 && i + k <= len)ans += ask(i - 1 , i + k);
        }
        printf("%I64d\n", ans);


    }
    return 0;
}


string string string

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 458    Accepted Submission(s): 108


Problem Description
Uncle Mao is a wonderful ACMER. One day he met an easy problem, but Uncle Mao was so lazy that he left the problem to you. I hope you can give him a solution.
Given a string s, we define a substring that happens exactly  k  times as an important string, and you need to find out how many substrings which are important strings.
 

Input
The first line contains an integer  T  ( T100 ) implying the number of test cases.
For each test case, there are two lines:
the first line contains an integer  k  ( k1 ) which is described above;
the second line contain a string  s  ( length(s)105 ).
It's guaranteed that  length(s)2106 .
 

Output
For each test case, print the number of the important substrings in a line.
 

Sample Input
  
2 2 abcabc 3 abcabcabcabc
 

Sample Output
  
6 9
 

Source
 

Recommend
liuyiding   |   We have carefully selected several similar problems for you:   6205  6204  6203  6202  6201 
 

Statistic |  Submit |  Discuss |  Note

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值