P3538-[POI2012]OKR-A Horrible Poem【hash,字符串】

正题

评测记录:https://www.luogu.org/recordnew/lists?uid=52918&pid=P3538


题目大意

给一个字符串,有q个询问,询问一个区间最短循环节。


解题思路

首先最短循环节长度一定长度的约数,所以我们可以枚举约数,然后判断循环节的话只需要 ( l , r t ) ( l + t , r ) 这段区间相等就好了,所以我们可以用hash表来 O ( 1 ) 来判断循环节。
时间复杂度: O ( q n )
然后在约数的地方我们用素数筛来做,将 n 的复杂度降低为了 l o g   n
最终时间复杂度: O ( q     l o g   n )


code

#include<cstdio>
#define ull unsigned long long
using namespace std;
const int wer=13131,N=500005;
ull mul[N],hash[N];
bool use[N];
int next[N],prim[N/10],ys[N/10],n,m,l,r,k,tot;
char s[N];
void prime()//素数筛优化约数
{
    for(int i=2;i<=n;i++)
    {
        if(!use[i]){
            prim[++k]=i;
            next[i]=i;
        }
        for(int j=1;j<=k&&(long long)prim[j]*i<=n;j++)
        {
            next[prim[j]*i]=prim[j];
            use[prim[j]*i]=true;
            if(i%prim[j]==0){
                break;
            }
        }
    }
}
bool check(int l1,int r1,int l2,int r2)
{
    return hash[r1]-hash[l1-1]*mul[r1-l1+1]==hash[r2]-hash[l2-1]*mul[r2-l2+1];
}
int main()
{
    scanf("%d\n%s",&n,&s);
    mul[0]=1;
    for(int i=1;i<=n;i++){
        mul[i]=mul[i-1]*wer;
        hash[i]=hash[i-1]*wer+s[i-1]-'a'+1;
    }//哈希
    prime();
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&l,&r);
        int len=r-l+1;
        tot=0;
        while(len!=1){
            ys[++tot]=next[len];//记录素数
            len/=next[len];
        }
        len=r-l+1;
        for(int j=1;j<=tot;j++)//枚举约数
        {
            int t=len/ys[j];
            if(check(l,r-t,l+t,r)){
                len=t;
            }//判断
        }
        printf("%d\n",len);
    }
}

猜你喜欢

转载自blog.csdn.net/Mr_wuyongcong/article/details/81746601