G - Maximum repetition substring POJ - 3693 (后缀数组)

本文介绍了一个使用后缀数组寻找字符串中最长重复子串的方法,并通过具体实现展示了如何找到具有最大重复次数且字典序最小的子串。该方法首先构建后缀数组并计算最长公共前缀(LCP),然后利用RMQ预处理加速查询过程。

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

又是一道后缀数组的题目,和上一道题目是类似的,不过要输字符串本身,在同等重复次数下输出,字典序最小的。
我们只需要记录下所有达到最大重复次数的,重复段的长度就可以了,然后按照sa数组排序好的字典序,对每一个sa[i],去枚举记录下来的重复长度,如果刚好达到要求直接输出就行了。

#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
#define Max_N (2*50000 + 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 buf[Max_N];
int f[Max_N][30];
void rmq()
{
    for (int i = 1; i <= n; i++)
        f[i][0] = lcp[i];
    f[n][0] = 1000000;
     for (int j=1; (1<<j) <= n ; j++)  
        for (int i=1; i+(1<<j)-1<=n; i++)  
            f[i][j]= min(f[i][j-1],f[i+(1<<j-1)][j-1]);  
    return;  
}

int lcp1(int L, int R)
{
    if (L> R) {
        int ss = R;
        R = L;
        L = ss;
    }
    R--;
    int k1 = 0;
    while((1<<(k1+1)) <= R-L+1) k1++;
    return min(f[L][k1], f[R-(1<<k1)+1][k1]);
}
vector<int> v;
int main()
{
    int T;
    T = 0;
    while (true) {
        scanf("%s", buf);
        if (buf[0] == '#') return 0;
        n = strlen(buf);
        for (int i = 0; i < n; i++)
            a[i] = int(buf[i]);
        a[n] = 0;
        construct_sa(a, n, sa);
        construct_lcp(a, n, sa, lcp);
        rmq();
        /*int l,r;
        cin >> l >> r;
        cout << lcp1(l, r) << endl;*/
        int max = 0;
        v.clear();
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j + i < n; j += i) {
                int k1 = lcp1(rank1[j], rank1[j+i]);
                int r = k1 / i + 1;
                int t = j - (i - k1%i);
                if (t >= 0)
                    if (lcp1(rank1[t], rank1[t+i]) >= i) {
                        r++;
                    }
                if (r > max) {
                    max = r;
                    v.clear();
                    v.push_back(i);

                }   
                if (r == max) {
                    v.push_back(i);
                }

                }
            }
        int beg = 0;
        int len = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < v.size(); j++) {
                if (sa[i] + v[j] <= n) {
                    if (lcp1(i, rank1[sa[i]+v[j]]) >= (max-1) * v[j]) {
                        beg = sa[i];
                        len = v[j] * max;
                        i = n+1;
                        j = v.size() + 1;
                    }
                }
            }
        }
        printf("Case %d: ", ++T);
        for (int i = beg; i < beg + len; i++)
            printf("%c", buf[i]);
        cout << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值