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

正解:SA/二分+哈希

解题报告:

传送门!

umm像这种子串的问题已经算是比较套路的了,,,?就后缀的公共前缀这样儿的嘛QwQ

所以可以先求个SA

然后现在考虑怎么判断一个长度为d的子串出现了k次?这儿可以直接判断是否有连续的k个hei[i]>=d,因为已经按字典序排序了,所以只要有连续的k个hei[i]>=d就一定有长度大于等于d的子串出现k次

所以二分了check一下就好

然后再说说二分+哈希的做法趴QwQ

就先二分一个长度d,然后把所有长度为d的子串的哈希值求出来,排个序之后判断有没有一个哈希值出现次数>=d

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define gc getchar()
#define ll long long
#define ri register int
#define rb register bool
#define rc register char
#define rp(i,x,y) for(ri i=x;i<=y;++i)
#define my(i,x,y) for(ri i=x;i>=y;--i)

const int N=1e6+20,inf=1e9;
int x[N],y[N],sa[N],rk[N],hei[N],t[N],n,K,ch[N],l,r;

il int read()
{
    rc ch=gc;ri x=0;rb y=1;
    while(ch!='-' && (ch>'9' || ch<'0'))ch=gc;
    if(ch=='-')ch=gc,y=0;
    while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=gc;
    return y?x:x;
}
il bool check(ri gd,ri gs,ri k){return y[gd]==y[gs] && y[gd+k]==y[gs+k];}
il void SA()
{
    ri m=N-10;
    rp(i,1,n)++t[x[i]=ch[i]];
    rp(i,1,m)t[i]+=t[i-1];
    my(i,n,1)sa[t[x[i]]--]=i;
    for(ri k=1;k<=n;k<<=1)
    {
        ri p=0;
        rp(i,0,m)t[i]=0;rp(i,1,n)y[i]=0;
        rp(i,n-k+1,n)y[++p]=i;rp(i,1,n)if(sa[i]>k)y[++p]=sa[i]-k;
        rp(i,1,n)++t[x[y[i]]];
        rp(i,0,m)t[i]+=t[i-1];
        my(i,n,1)sa[t[x[y[i]]]--]=y[i];
        swap(x,y);
        x[sa[1]]=p=1;
        rp(i,2,n)x[sa[i]]=check(sa[i],sa[i-1],k)?p:++p;
        if(p>=n)break;
        m=p;
    }
    rp(i,1,n)rk[sa[i]]=i;
    ri h=0;
    rp(i,1,n)
    {
        if(h)--h;
        while(ch[i+h]==ch[sa[rk[i]-1]+h])++h;
        hei[rk[i]]=h;r=max(r,h);
    }
}
il bool jud(ri x)
{
    ri cnt=0;
    rp(i,2,n){if(hei[i]<x)cnt=0;else ++cnt;if(cnt==K-1)return true;}return false;
}

int main()
{
//     freopen("2852.in","r",stdin);freopen("2852.out","w",stdout);
    n=read();K=read();rp(i,1,n)ch[i]=read();SA();r=n;
    while(l<r){ri mid=(l+r)>>1;if(jud(mid+1))l=mid+1;else r=mid;}
    printf("%d\n",l);
    return 0;
}
啊我麻油写二分+哈希,,,就写了个SA的代码QwQ

猜你喜欢

转载自www.cnblogs.com/lqsukida/p/10652798.html