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

产奶模式「USACO 2006」

时间:2020-01-21 00:56:23      阅读:98      评论:0      收藏:0      [点我收藏+]

标签:不可   一段   get   相同   奶牛   证明   描述   区间   std   

【题目描述】
农夫John发现他的奶牛产奶的质量一直在变动。经过细致的调查,他发现:虽然他不能预见明天产奶的质量,但连续的若干天的质量有很多重叠。我们称之为一个“模式”。 John的牛奶按质量可以被赋予一个\(0\)\(1000000\)之间的数。并且John记录了\(N(1\le N\le 20000)\)天的牛奶质量值。他想知道最长的出现了至少\(K(2\le K\le N)\)次的模式的长度。比如1 2 3 2 3 2 3 1 中 2 3 2 3出现了两次。当\(K=2\)时,这个长度为4。

【输入格式】
Line 1: 两个整数 N,K。
Lines 2..N+1: 每行一个整数表示当天的质量值。

【输出格式】
Line 1: 一个整数:N天中最长的出现了至少K次的模式的长度

题解

后缀数组+LCP(最长公共前缀)
这个组合的好处是什么呢 一个字符串中的每个子串都必然是一个后缀的前缀

定义suf(i)表示s[i~n]
定义height[i]表示suf(sa[i-1])与suf(sa[i])的最长公共前缀 即排名为i的后缀与他排名前一个的后缀的LCP
关于height数组这里只给出求法 具体证明请自行百度

void geth() {
    int p = 0;
    for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
    for (int i = 1; i <= n; i++) {
        if (p) p--; int j = sa[rnk[i]-1];
        while (s[i+p] == s[j+p]) p++;
        height[rnk[i]] = p;
    }
}

引理1
\(LCP(suf(x), suf(y)) = min_{i=rnk[x]+1}^{rnk[y]} height[i]\)
证明略(其实是懒)

假设现在要找一个最长的子串 使得这个子串在原串中出现至少2次(可重叠)
只需要找出height数组的最大值即是答案
证明:假设\(LCP(suf(x), suf(y))=k\) 不妨设rnk[x]<rnk[y] 那么由引理1 一定有\(LCP(suf(x), suf(sa[rnk[x]+1]))=height[rnk[x]+1]\ge k\)
(sa[rnk[x]+1]即排名在suf(x)后一名的后缀)

那么如果出现至少3次呢 同理可得 如果存在一对连续的height[i],height[i+1]均大于等于m 就存在一个至少出现3次的长为m的子串

得出一般性结论 如果能找到一段连续k-1个height[i+1]~height[i+k-1]均大于等于m 那么就存在一个出现至少k次的长为m的子串
由引理1 这段区间内的k个后缀suf(sa[i])~suf(sa[i+k-1])必然是两两有长度至少为m的LCP

对于这题 可以二分答案mid 然后判断是否存在一段k-1个height全部大于等于mid
读入数据可以离散化一下 不离散化也无所谓 时间复杂度\(O(n\ log\ n)\)

扩展: 求一个最长子串使得这个子串在原串中出现至少2次(不可重叠)
方法: 二分答案 找出每一段 连续一些height全部大于二分值mid的区间 形如height[l+1]~height[r]
如果\(max_{i=l}^{r}sa[i]-min_{i=l}^{r}sa[i]\ge mid\) 则必定存在不重叠的长为mid的相同子串(自行理解一下)

代码

#include <bits/stdc++.h>
#define mx 1000005
using namespace std;

int s[mx], srt[mx], mxx;
int n, m, k, ans;
int sa[mx], sa2[mx], rnk[mx], key[mx], sum[mx], height[mx];

inline bool check(int *num, int a, int b, int l) { return num[a] == num[b] && num[a+l] == num[b+l]; }

inline void suffix() {
    int i, j, p; int *_rnk = rnk, *_sa2 = sa2, *tmp;
    for (i = 1; i <= m; i++) sum[i] = 0;
    for (i = 1; i <= n; i++) sum[_rnk[i]=s[i]]++;
    for (i = 2; i <= m; i++) sum[i] += sum[i-1];
    for (i = n; i >= 1; i--) sa[sum[_rnk[i]]--] = i;
    for (j = 1; p <= n; j <<= 1, m = p) {
        p = 0;
        for (i = n - j + 1; i <= n; i++) _sa2[++p] = i;
        for (i = 1; i <= n; i++) if (sa[i] > j) _sa2[++p] = sa[i] - j;
        for (i = 1; i <= n; i++) key[i] = _rnk[_sa2[i]];
        for (i = 1; i <= m; i++) sum[i] = 0;
        for (i = 1; i <= n; i++) sum[key[i]]++;
        for (i = 2; i <= m; i++) sum[i] += sum[i-1];
        for (i = n; i >= 1; i--) sa[sum[key[i]]--] = _sa2[i];
        for (tmp = _rnk, _rnk = _sa2, _sa2 = tmp, p = 2, _rnk[sa[1]] = 1, i = 2; i <= n; i++) {
            _rnk[sa[i]] = check(_sa2, sa[i-1], sa[i], j) ? p - 1 : p++;
        }
    }
} 

inline void geth() {
    int p = 0;
    for (int i = 1; i <= n; i++) rnk[sa[i]] = i;
    for (int i = 1; i <= n; i++) {
        if (p) p--; int j = sa[rnk[i]-1];
        while (s[i+p] == s[j+p]) p++;
        height[rnk[i]] = p;
    }
}

inline bool check(int mid) {
    int p = 0;
    for (int i = 2; i <= n; i++) {
        if (height[i] >= mid) p++;
        else p = 0;
        if (p >= k - 1) return 1;
    }
    return 0;
}

int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &s[i]); srt[i] = s[i];
    }
    sort(srt+1, srt+n+1);
    mxx = unique(srt+1, srt+n+1)-srt-1;
    for (int i = 1; i <= n; i++) {
        s[i] = lower_bound(srt+1, srt+mxx+1, s[i]) - srt;
    }
    m = 1000000;
    suffix(); geth();
    int l = 1, r = n, mid;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (check(mid)) {
            ans = mid; l = mid + 1;
        } else r = mid - 1;
    }
    printf("%d\n", ans);
    return 0;
} 

产奶模式「USACO 2006」

标签:不可   一段   get   相同   奶牛   证明   描述   区间   std   

原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM28.html

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