码迷,mamicode.com
首页 > 其他好文 > 详细

题解-牛奶模式

时间:2019-02-03 18:11:59      阅读:184      评论:0      收藏:0      [点我收藏+]

标签:scanf   n+1   题目   就是   出现   传送门   line   get   长度   


(〇)题目描述

题目传送门

简单讲解一下题意:

给出一个字符串,求最长至少出现了 \(k\) 的子串(可重叠)。

(一)解题思路

这题需要我们在一个模式串中找相同的子串,很容易就能想到后缀数组。

那么,如何找至少重复出现 \(k\) 次的子串呢?

考虑二分子串的长度,看看答案是否具有单调性。

如果长度为 \(len\) 的子串出现了 \(k\) 次,那么一定有长度小于 \(len\) 的子串出现了 \(k\) 次(这些子串可以是长度为 \(len\) 的子串的子串)

这样题目就变成了判定性问题,我们只需判断是否有长度为 \(len\) 子串出现次数\(\geqslant k\)

问题又来了,如何判断字符串中是否有至少重复出现 \(k\) 次的子串呢?

要找重复 \(k\) 次的子串,其实就是找 \(k\) 个相同的子串。找相同的子串,可以考虑使用后缀数组。对于得到的 \(height[~]\) 数组,使用分组的方法,使得每组中的每个后缀的最长公共前缀都 \(\geqslant len\) ,再判断是否有一组中的后缀数量 \(\geqslant k\) 即可。

(二)解题方法

做法:二分答案+后缀数组

时间复杂度:\(\Theta(n\log_2{n})\)

二分答案不必讲,求后缀数组和 \(height[~]\) 数组可以参考论文或者学习总结-后缀数组

然后...就没有然后了,直接上代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
int sa[maxn], rak[maxn], sum[maxn], height[maxn];
int key2[maxn], newRak[maxn], a[maxn];
int N, K, M;
bool cmp(int a, int b, int l)
{
    if(rak[a] != rak[b]) return false;
    if((a+l > N) xor (b+l > N)) return false;
    if(a+l > N and b+l > N) return true;
    return rak[a+l] == rak[b+l];
}
void getHeight()
{
    int k = 0;
    for(int i=1; i<=N; i++)
    {
        if(rak[i] == 1) {height[rak[i]] = 0; continue;}
        int j = sa[rak[i]-1];
        while(a[i+k] == a[j+k]) k++;
        height[rak[i]] = k;
        if(k != 0) k--;
    }
}
bool ok(int x)
{
    int cnt = 1;
    for(int i=2; i<=N; i++)
    {
        if(height[i] < x) cnt = 1;
        else cnt++;
        if(cnt >= K) return true;
    }
    return false;
}
int main()
{
    scanf("%d%d",&N,&K);
    for(int i=1; i<=N; i++) scanf("%d",&a[i]);
    M = max(1000000, N);
    for(int i=1; i<=N; i++) sum[rak[i] = a[i]]++;
    for(int i=1; i<=M; i++) sum[i] += sum[i-1];
    for(int i=N; i>=1; i--) sa[sum[rak[i]]--] = i;
    for(int l=1; l<N; l<<=1)
    {
        int k = 0;
        for(int i=N-l+1; i<=N; i++) key2[++k] = i;
        for(int i=1; i<=N; i++) if(sa[i] > l) key2[++k] = sa[i]-l;
        for(int i=1; i<=M; i++) sum[i] = 0;
        for(int i=1; i<=N; i++) sum[rak[i]]++;
        for(int i=1; i<=M; i++) sum[i] += sum[i-1];
        for(int i=N; i>=1; i--) sa[sum[rak[key2[i]]]--] = key2[i];
        int rk = 1;
        newRak[sa[1]] = 1;
        for(int i=2; i<=N; i++)
            if(cmp(sa[i], sa[i-1], l)) newRak[sa[i]] = rk;
            else newRak[sa[i]] = ++rk;
        for(int i=1; i<=N; i++) rak[i] = newRak[i];
        if(rk == N) break;
    }
    getHeight();
    int l = 0, r = N+1;
    while(l+1 < r)
    {
        int mid = (l+r)>>1;
        if(ok(mid)) l = mid;
        else r = mid;
    }
    printf("%d", l);
    return 0;
} 

题解-牛奶模式

标签:scanf   n+1   题目   就是   出现   传送门   line   get   长度   

原文地址:https://www.cnblogs.com/GDOI2018/p/10350656.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!