[POI 2012]A Horrible Poem(字符串Hash)

题目链接

http://main.edu.pl/en/archive/oi/19/okr

题目大意

给出一个字符串,多次询问其中一个子串 [L,R] 的最小循环节长度。

思路

假设 Ri=L(S[i]==x) 表示区间 [L,R] 里字母 x 的出现次数。

假设最小循环节长度为 t ,则 RL+1t (最小循环节的出现次数)是 gcdt{Ri=L(S[i]==t)} 的约数。

因此我们可以枚举 gcdt{Ri=L(S[i]==t)} 的约数,得到枚举出的最小循环节的长度,这样枚举最小循环节的长度,枚举次数会很少。一个合法的循环节长度可以取 l ,当且仅当 [L,Rl] 部分和 [L+l,R] 相同,这个可以自行yy下为啥是正确的。。。详细证明不好证。。。这样的话,我们在枚举了最小循环节的长度后就可以用hash来 O(1) 判断所枚举的循环节长度是否是合法的了

代码

#include 
#include 
#include 
#include 
#include 

#define MAXN 510000
#define MOD 1000000009

using namespace std;

typedef long long int LL;

char s[MAXN];
LL hash[MAXN],pow[MAXN];
int n,q;
int sum[MAXN][26];

int gcd(int a,int b)
{
    if(!b) return a;
    return gcd(b,a%b);
}

bool check(int L1,int R1,int L2,int R2)
{
    LL hash1=((hash[R1]-pow[R1-L1+1]*hash[L1-1]%MOD)%MOD+MOD)%MOD;
    LL hash2=((hash[R2]-pow[R2-L2+1]*hash[L2-1]%MOD)%MOD+MOD)%MOD;
    return hash1==hash2;
}

int main()
{
    scanf("%d",&n);
    scanf("%s",s+1);
    pow[0]=1;
    for(int i=1;i<=n;i++)
        pow[i]=pow[i-1]*30007%MOD;
    for(int i=1;i<=n;i++)
        hash[i]=(hash[i-1]*30007%MOD+s[i]-'a'+1)%MOD;
    for(int i=1;i<=n;i++)
        for(int alpha=0;alpha<26;alpha++)
        {
            sum[i][alpha]=sum[i-1][alpha];
            sum[i][alpha]+=((s[i]-'a')==alpha);
        }
    scanf("%d",&q);
    while(q--)
    {
        int L,R,ans=0;
        scanf("%d%d",&L,&R);
        int GCD=0,len=R-L+1;
        for(int alpha=0;alpha<26;alpha++)
            GCD=gcd(GCD,sum[R][alpha]-sum[L-1][alpha]);
        for(int i=1;i*i<=GCD;i++)
        {
            if(check(L,R-len/i,L+len/i,R)) ans=max(ans,i);
            if(check(L,R-len/(GCD/i),L+len/(GCD/i),R)) ans=max(ans,GCD/i);
        }
        printf("%d\n",len/ans);
    }
    return 0;
}

你可能感兴趣的:(MAIN,传统题,字符串,POI)