JZOJ 3977 密码破译

密码破译

Description

给出一个长度为 n ,由小写字母组成的母串。
给出q组询问,每次询问母串的一个子串的关键值。
一个字符串的价值定义为一个最短的 T (某个字符串),使得S= T K ,即 K T依次首尾相连连在一起。
则该字符串的关键值即为 T 的长度。

Data Constraint

n 500000 , m 2000000

Solution

这是一道伪装的水题。
题目让我们求母串的一个子串的最短循环节。

接下来给出两个显然的结论(不给出证明了,请读者自行思考)
1、串 T 有一个长度为Len循环节的充要条件为 T 的前N Len 位与后 N Len位是一样的。
2、若一个长度为 Len 的前缀不是 T 的循环节,那么Len的所有约数肯定也不是可行的循环节长度。

有了以上两个结论这题就好做了。
一开始设它的 ans 为子串的长度 Len ,枚举 Len 的所有质因子,假设枚举到当前的质因子为 p ,若ans/ p 是一个可行的循环节长度,那么就让ans变成 ans / p ,这样做显然是对的。

至于判断两个子串是否相同,可以用hash O(1) 判断。(为了防卡我打了双 hash
质因子可以用线筛预处理好。
由于每个质因子都大于等于 2 ,所以Len的质因子个数不会超过log2 Len
所以总的复杂度为 O (m log n <script type="math/tex" id="MathJax-Element-42">n</script>)。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define fo(i,j,l) for(int i=j;i<=l;++i)
#define fd(i,j,l) for(int i=j;i>=l;--i)

using namespace std;
typedef long long ll;
const ll N=52e4,k1=23,k2=29,shit=666,F_word=168;
const ll m1=1e9+7,m2=998244353,m3=1004535809;
char s[N];
ll s1[N],s2[N],j1[N],j2[N],n1[N],n2[N];
bool bz[N];
int from[N],ss[N],n,q;

inline ll ksm(ll o,ll t,ll mo)
{
    ll y=1;
    for(;t;t>>=1,o=o*o%mo)
    if(t&1)y=y*o%mo;
    return y;
}

inline int read()
{
    int o=0; char ch=' ';
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;
    return o;
}

ll get(int a,int b)
{
    ll k1=(s1[b]-s1[a-1]+m1*2)*n1[a]%m1;
    ll k2=(s2[b]-s2[a-1]+m2*2)*n2[a]%m2;
    return (k1*shit+k2*F_word)%m3;
}

int main()
{
    cin>>n;
    scanf("%s",s+1);
    s1[1]=s2[1]=s[1]-97; j1[1]=j2[1]=1;
    fo(i,2,n)
    {
        j1[i]=j1[i-1]*k1%m1;
        j2[i]=j2[i-1]*k2%m2;
        s1[i]=(s1[i-1]+j1[i]*(s[i]-97))%m1;
        s2[i]=(s2[i-1]+j2[i]*(s[i]-97))%m2;
    }
    n1[n]=ksm(j1[n],m1-2,m1);
    n2[n]=ksm(j2[n],m2-2,m2);
    fd(i,n-1,1)n1[i]=n1[i+1]*k1%m1,n2[i]=n2[i+1]*k2%m2;
    n1[0]=n2[0]=1;
    int o=0;
    fo(i,2,n)
    {
        if(!bz[i])ss[++o]=i,from[i]=i;
        fo(j,1,o){
            if(i*ss[j]>n)break;
            from[i*ss[j]]=ss[j];
            bz[i*ss[j]]=true;
            if(i%ss[j]==0)break;
        }
    }
    cin>>q;
    fo(i,1,q){
        int le=read(),ri=read();
        int len=ri-le+1;
        int k=len,ans=k;
        for(;k!=1;k/=from[k])
        if(ans%from[k]==0)
        if(get(le,ri-ans/from[k])==get(le+ans/from[k],ri))ans/=from[k];
        printf("%d\n",ans);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值