题解-牛奶模式


(〇)题目描述

题目传送门

简单讲解一下题意:

给出一个字符串,求最长至少出现了 \(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;
} 

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

时间: 2024-08-30 18:09:46

题解-牛奶模式的相关文章

洛谷P2852 [USACO06DEC]牛奶模式Milk Patterns

题目描述 Farmer John has noticed that the quality of milk given by his cows varies from day to day. On further investigation, he discovered that although he can't predict the quality of milk from one day to the next, there are some regular patterns in th

USACO06DEC 牛奶模式

题意:求最长的可重叠的 K重复子串 的长度 考虑二分长度s,转化为验证性问题. 对SA进行分组.保证组内Height最小为s.这样在组内RMQ就可以任意了,因为RMQ一定是大于S的. 只要组内元素个数大于等于K就是可行解. 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 struct SA{ 5 int str[1000005]; 6 int x[1000005],y[1000005],u[1000005],v[1000005],r[

[Luogu2852][USACO06DEC]牛奶模式Milk Patterns

Luogu 一句话题意 给出一个串,求至少出现了\(K\)次的子串的最长长度. sol 对这个串求后缀数组. 二分最长长度. 如果有\(K\)个不同后缀他们两两的\(lcp\)都\(>=mid\) 那么他们在\(SA\)中一定排在连续的一段区间,且两两之间的\(Height[i]>=mid\) 所以判断\(Height\)数组中是否存在长度大于等于\(K-1\)且数值全部大于等于\(mid\)的连续段. code #include<cstdio> #include<algor

Luogu2852 [USACO06DEC]牛奶模式

题目蓝链 Description 给定一个字符串,你需要找到一个最长在这个串中至少出现了\(k\)次的子串 Solution 我们首先对这个串进行后缀排序,那么对于排序后的任意一个区间\([l, r]\),那么原串中一定有\(r - l + 1\)个长度为\(MIN_{i \in (l, r]} \{height_i\}\)的相同字串 于是我们就直接把\(height\)数列扫一边,同时维护一个单调队列,来维护任意相邻的\(k - 1\)个\(height\)的最小值,答案便是这些最小值中的最大

后缀数组题目

巨佬博客: https://www.cnblogs.com/zwfymqz/p/8413523.html #include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=1e6+10; int n,M,sa[N],rk[N],tp[N],sum[N],hight[N]; char s[N]; void Qsort() { for(int i=0;i<=M;i++) sum[i]=0; for(i

2019年7月博客汇总下

[ZJOI2007]捉迷藏 这是我最近写过最长的代码QAQ 码力太弱了QAQ 动态点分治模板题. 我们可以用三种堆来维护答案,这些堆要求支持删除非顶元素,以及查询次小值.我们把两个STL堆封装起来就可以实现. 三种堆: d[x]表示以x为根的点分树中所有黑点到它分治爹的距离 c[x]表示以x为根的所有点分儿子d堆中的最大值 ans表示全局的最大值 我们从c中取出最大值和次大值就可以得到过这个点分根的最长链.我们不断用它来更新答案. 注意d的定义是到分治父亲的父亲的最大值 c数组要加入一个0 Co

关于jzyzoj——P1341:被污染的牛奶的题解探讨

题面: 描述 Description 你第一天接手三鹿牛奶公司就发生了一件倒霉的事情:公司不小心发送了一批有三聚氰胺的牛奶.很不幸,你发现这件事的时候,有三聚氰胺的牛奶已经进入了送货网.这个送货网很大,而且关系复杂.你知道这批牛奶要发给哪个零售商,但是要把这批牛奶送到他手中有许多种途径.送货网由一些仓库和运输卡车组成,每辆卡车都在各自固定的两个仓库之间单向运输牛奶.在追查这些有三聚氰胺的牛奶的时候,有必要保证它不被送到零售商手里,所以必须使某些运输卡车停止运输,但是停止每辆卡车都会有一定的经济损

[题解]第十一届北航程序设计竞赛预赛——A.模式

题目描述 输入一个学号,判断是计算机系or软件学院or其他院系. 解题思路 水题,直接判断or除以10000都可以.不废话,直接上代码. 1 #include <iostream> 2 #include <cstdio> 3 4 using namespace std; 5 6 int main() 7 { 8 char s[20]; 9 while(scanf("%s", s) != EOF) 10 { 11 if(s[2] == '0' &&

BZOJ 1717: [Usaco2006 Dec]Milk Patterns 产奶的模式

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