Codeforces Contest 526 problem D —— 后缀数组处理所有后缀与整个字符串的最长公共前缀 + 双哈希

This way

题意:

给你一个字符串,如果从开头到某一个位置的格式是A+B+A+B+…+A的形式,(A有k+1个,b有k个,A,B可以为空)那么这里就输出1,否则输出0.

题解:

我们将A+B看做C,那么就是C+C+C+…+A,也就是k个相同的字符串加上一个C的前缀。那么我们就用后缀数组处理出所有后缀与这整个字符串的最长前缀,记到same数组里,这样的话我们在求A的时候直接算上这个位置的same,由于A是C的子串,所以要取个min。那怎么求same数组?
我们知道height[i]是排名为i与排名为i-1的后缀的最长公共前缀,那么可以从rk[0]这个位置往两边扩出去,但是由于height数组是波澜起伏的一个数组,所以每次都要取个min,比如说有
aaa
aaaa
aab
假设整个字符串是以aab为前缀的,前面的两行是这个字符串的某一个后缀,那么height[3]=2,height[2]=3,height[1]=0,显然第一个后缀与第三个后缀的最长公共前缀是2,所以要取min。
之后我们枚举C的长度即可,查找每一段是否相等用哈希,注意它有1e6个字符串,所以单哈希和unsigned long long 自动取模都不行,或者你运气够好。反正我是用双哈希才过的,双哈希就是用一个pair,两边分别保存哈希时候乘上的不同的数,假设hash=hash*k+a[i],这个保存的就是不同的k,我这里是用31与23。
最后如果实时更新的话会T,所以我们用差分维护哪一段有即可。
但是这个方法肯定不是最佳方法,因为我是刚刚好卡过去的
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pl pair<ll,ll>
const ll mod=1e9+7;
const int N=1e6+5;
int wa[N],wb[N],wv[N],we[N],rk[N];
int cmp(int *r,int a,int b,int l){return r[a]==r[b]&&r[a+l]==r[b+l];}
void build_sa(char *r,int *sa,int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)we[i]=0;
    for(i=0;i<n;i++)we[x[i]=r[i]]++;
    for(i=1;i<m;i++)we[i]+=we[i-1];
    for(i=n-1;i>=0;i--)sa[--we[x[i]]]=i;
    for(j=1,p=1;p<n;j*=2,m=p){
        for(p=0,i=n-j;i<n;i++)y[p++]=i;
        for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
        for(i=0;i<n;i++)wv[i]=x[y[i]];
        for(i=0;i<m;i++)we[i]=0;
        for(i=0;i<n;i++)we[wv[i]]++;
        for(i=1;i<m;i++)we[i]+=we[i-1];
        for(i=n-1;i>=0;i--)sa[--we[wv[i]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
int height[N];
void calheight(char *r,int *sa,int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)rk[sa[i]]=i;
    for(i=0;i<n;height[rk[i++]]=k){
        for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
    }
}
int sa[N],same[N];
char a[N];
pl has[N],p[N];
pl get_hash(int l,int r)
{
    return (pl){(has[r].first-has[l].first*p[r-l].first%mod+mod)%mod,(has[r].second-has[l].second*p[r-l].second%mod+mod)%mod};
}
int ans[N];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    scanf("%s",a);
    if(k==1)
    {
        for(int i=1;i<=n;i++)
            printf("1");
        printf("\n");
        return 0;
    }
    a[n]='0';
    build_sa(a,sa,n+1,300);
    calheight(a,sa,n);
    int mx=height[rk[0]];
    same[0]=mx;
    for(int i=rk[0];height[i]>0&&i>0;i--)
        same[sa[i-1]]=min(mx,height[i]),mx=min(mx,height[i]);
    mx=height[rk[0]+1];
    for(int i=rk[0]+1;height[i]>0&&i<n;i++)
        same[sa[i]]=min(mx,height[i]),mx=min(mx,height[i]);
    pl h=(pl){0,0};
    p[0]={1,1};
    for(int i=0;i<n;i++)
        h.first=(h.first*31+(ll)a[i])%mod,h.second=(h.second*23+(ll)a[i])%mod,has[i]=h;
    for(int i=1;i<=n;i++)
        p[i].first=p[i-1].first*31%mod,p[i].second=p[i-1].second*23%mod;
    for(int len=1;len*k<=n;len++)
    {
        h=has[len-1];
        for(int i=1;i<k;i++)
        {
            if(get_hash(i*len-1,i*len+len-1)!=h)
                break;
            if(i==k-1)
                ans[len*i+len-1]++,ans[len*i+len+min(len,same[len*i+len])]--;
        }
    }
    int sum=0;
    for(int i=0;i<n;i++)
        sum+=ans[i],printf("%d",sum>0?1:0);
    return 0*printf("\n");
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值