bzoj2795[Poi2012]A Horrible Poem 暴力hash

来源:互联网 发布:snmp trap 端口号 编辑:程序博客网 时间:2024/06/06 03:31

题意:给你一个串,一些询问,求l,r子串内的最小循环节。|s|<=5e5,q<=2e6;
询问那么大,肯定不能搞事,然后我就想到了SAM。。。。。。
SAM明显搞不了啊,然后就萎了,想不到该咋搞。。
事实上暴力hash就可以了。。。
首先hash一波,枚举循环节长度以后,暴力O(1)判断循环节。
循环节长度不能无脑枚举,由于是循环节,那肯定是n的约数才有可能,所以根号搞一波。
但这样还是有一点慢,注意到循环节的长度也是字符出现次数的gcd,所以一起取个gcd就ok了。
至于O(1)判断的话,假设当前长度为len,当s[1…n-len]=s[len+1…n]的时候就成立。
这个很显然,跟那个数列里面的错位相减法差不多,就是假设循环节为w1,w2….wn,我现在要求w1=w2=w3=w4…..
那么s[1….n-len]=w1+w2+…+wn-1
s[len+1….n]=w2+…..+wn
这样就很明显了。

#include<cstdio>#include<algorithm>#include<cstring>#define fo(i,a,b) for(int i=a;i<=b;i++)#define fd(i,a,b) for(int i=a;i>=b;i--)using namespace std;const int N=5e5+5;const int M=5e5+5;const int mo1=233333333;const int mo2=1e9+7;int n,q,s[N][28];int bin1[N],bin2[N];int hash1[N],hash2[N];char ch[N];inline int gcd(int a,int b){    if (a%b==0)return b;    else return gcd(b,a%b);}inline bool check(int x,int y,int len){    int x1=(hash1[x+len-1]-1ll*hash1[x-1]*bin1[len]%mo1+mo1)%mo1;    int y1=(hash2[x+len-1]-1ll*hash2[x-1]*bin2[len]%mo2+mo2)%mo2;    int x2=(hash1[y+len-1]-1ll*hash1[y-1]*bin1[len]%mo1+mo1)%mo1;    int y2=(hash2[y+len-1]-1ll*hash2[y-1]*bin2[len]%mo2+mo2)%mo2;    if (x1==x2&&y1==y2)return 1;    return 0;}int main(){    scanf("%d",&n);    scanf("%s",ch+1);    bin1[0]=bin2[0]=1;    fo(i,1,n)    {        fo(j,0,25)s[i][j]=s[i-1][j];        s[i][ch[i]-'a']++;        hash1[i]=(1ll*hash1[i-1]*27%mo1+ch[i]-'a'+1)%mo1;        hash2[i]=(1ll*hash2[i-1]*27%mo2+ch[i]-'a'+1)%mo2;        bin1[i]=1ll*bin1[i-1]*27%mo1;        bin2[i]=1ll*bin2[i-1]*27%mo2;    }    scanf("%d",&q);    fo(i,1,q)    {        int l,r;        scanf("%d%d",&l,&r);        int len=r-l+1,k=len;        fo(j,0,25)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)continue;            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);    }}
原创粉丝点击