bzoj 2795: [Poi2012]A Horrible Poem hash

题意

给出一个由小写英文字母组成的字符串S,再给出q个询问,要求回答S某个子串的最短循环节。
如果字符串B是字符串A的循环节,那么A可以由B重复若干次得到。
n<=500000,q<=2000000

分析

一开始啥都想不到,然后才发现原来正解就是暴力。

对于每一个区间,设其长度为len,其循环长度一定为n的约数。那么我们可以枚举循环节长度,是否是循环节的话就可以用hashO(1)判断了。
问题是这样比较慢,考虑优化。
其循环节的周期必然也是字符串中每个字符出现次数的约数,那么只要把len与所有字符出现的次数取个gcd,然后再O(sqrtn)枚举约数就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=500005;
const int MOD1=233333333;
const int MOD2=1000000007;

int n,q,s[N][27],mi1[N],mi2[N],hash1[N],hash2[N];
char ch[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int gcd(int x,int y)
{
    if (!y) return x;
    else return gcd(y,x%y);
}

bool check(int x,int y,int len)
{
    int u1=(hash1[x+len-1]-(LL)hash1[x-1]*mi1[len]%MOD1+MOD1)%MOD1;
    int v1=(hash2[x+len-1]-(LL)hash2[x-1]*mi2[len]%MOD2+MOD2)%MOD2;
    int u2=(hash1[y+len-1]-(LL)hash1[y-1]*mi1[len]%MOD1+MOD1)%MOD1;
    int v2=(hash2[y+len-1]-(LL)hash2[y-1]*mi2[len]%MOD2+MOD2)%MOD2;
    if (u1==u2&&v1==v2) return 1;
    else return 0;
}

int main()
{
    n=read();
    scanf("%s",ch+1);
    mi1[0]=mi2[0]=1;
    for (int i=1;i<=n;i++)
    {
        for (int j=0;j<26;j++) s[i][j]=s[i-1][j];
        s[i][ch[i]-'a']++;
        hash1[i]=((LL)hash1[i-1]*27%MOD1+ch[i]-'a'+1)%MOD1;
        hash2[i]=((LL)hash2[i-1]*27%MOD2+ch[i]-'a'+1)%MOD2;
        mi1[i]=(LL)mi1[i-1]*27%MOD1;
        mi2[i]=(LL)mi2[i-1]*27%MOD2;
    }
    q=read();
    for (int i=1;i<=q;i++)
    {
        int l=read(),r=read();
        int len=r-l+1,k=len;
        for (int j=0;j<26;j++) if (s[r][j]-s[l-1][j]) k=gcd(k,s[r][j]-s[l-1][j]);
        int ans=1;
        for (int j=1;j*j<=k;j++)
            if (k%j==0)
            {
                if (check(l,l+len/j,len-len/j)) ans=max(ans,j);
                if (check(l,l+len/(k/j),len-len/(k/j))) ans=max(ans,k/j);
            }
        printf("%d\n",(r-l+1)/ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值