牛客练习赛61 E 相似的子串 二分+哈希+dp

54 篇文章 1 订阅
32 篇文章 1 订阅

https://ac.nowcoder.com/acm/contest/5026/E
在这里插入图片描述思路:其实就是找一个长度为 x x x的字串,使得它在字符串 s s s中至少出现了 k k k次,且这 k k k s s s均不相交。很显然,长度 x x x满足单调性,所以我们可以二分长度。那么如何快速判断两个字串是否相等呢?哈希。我们设进制为 b a s e base base h [ i ] h[i] h[i]表示 s [ 1 … i ] s[1…i] s[1i]的哈希值,那么有: h [ i ] = ( s [ i ] − ‘ a ’ ) + ( s [ i − 1 ] − ‘ a ’ ) ∗ b a s e + … + ( s [ i − l e n + 1 ] ) ∗ b a s e l e n − 1 + ( s [ i − l e n ] ) ∗ b a s e l e n ) + … h[i]=(s[i]-‘a’)+(s[i-1]-‘a’)*base+…+(s[i-len+1])*base^{len-1}+(s[i-len])*base^{len})+… h[i]=(s[i]a)+(s[i1]a)base++(s[ilen+1])baselen1+(s[ilen])baselen)+。我们再设 b s [ 0 ] = 1 , b s [ i ] = b s [ i − 1 ] ∗ b a s e bs[0]=1,bs[i]=bs[i-1]*base bs[0]=1,bs[i]=bs[i1]base。那么子串 s [ i − l e n + 1 , i ] s[i-len+1,i] s[ilen+1,i]的哈希值就等于 h [ i ] − h [ i − l e n ] ∗ b a s e l e n h[i]-h[i-len]*base^{len} h[i]h[ilen]baselen。也就是说只要预处理出这两个数组,我们就可以 O ( 1 ) O(1) O(1)得到任意子串的哈希值。现在考虑怎么 d p dp dp。假设当前二分出的长度为 l e n len len,那么我们从 i = l e n i=len i=len开始向后扫,那么每次可以得到子串 s [ i − l e n + 1 , i ] s[i-len+1,i] s[ilen+1,i]的哈希值 r e s res res,开两个哈希表 p o s 、 c n t pos、cnt poscnt p o s [ i ] pos[i] pos[i]表示哈希值为 i i i的子串上一次出现的位置, c n t [ i ] cnt[i] cnt[i]表示哈希值为 i i i的子串的个数。那么如果 p o s [ r e s ] = 0 pos[res]=0 pos[res]=0,即该子串第一次出现,直接令 p o s [ r e s ] = i , c n t [ r e s ] = 1 pos[res]=i,cnt[res]=1 pos[res]=i,cnt[res]=1即可;否则我们判断当前位置和上一次出现的位置之间的距离是否 > = l e n >=len >=len(因为不能相交),再做修改即可。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

const int maxn=2e5+5;
const int base=127;

int n,k;
char s[maxn];
ull h[maxn],bs[maxn];
unordered_map<ull,int> pos,cnt;

bool check(int len)
{
    pos.clear(),cnt.clear();
    ull tmp;
    for(int i=len;i<=n;i++)
    {
        tmp=h[i]-h[i-len]*bs[len];
        if(!pos[tmp])
            pos[tmp]=i,cnt[tmp]=1;
        else if(i-pos[tmp]>=len)
            pos[tmp]=i,++cnt[tmp];
        if(cnt[tmp]>=k)
            return 1;
    }
    return 0;
}

int main()
{
    scanf("%d%d%s",&n,&k,s+1);
    bs[0]=1;
    for(int i=1;i<=n;i++)
    {
        h[i]=h[i-1]*base+s[i]-'a';
        bs[i]=bs[i-1]*base;
    }
    int l=1,r=n/k,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(check(mid))
            l=mid+1;
        else
            r=mid-1;
    }
    printf("%d\n",r);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值