POI 2012 OKR - A Horrible Poem 题解

本文探讨了一种优化的算法,用于查找字符串中最短的循环节。通过枚举字符串长度的因数并使用哈希匹配,文章提出了一种更高效的算法实现,避免了传统暴力匹配的高时间复杂度。此外,还介绍了如何利用质因数分解和特定的性质来进一步减少计算量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

题目大意: 有一个长度为 n n n的字符串,每次询问一个字串的最短循环节。

题解

最最暴力的做法:枚举长度 l e n len len,每次将该长度的字串与原串暴力匹配一遍。

然后考虑优化:

1.

显然 l e n len len 一定是 n n n 的因数,于是枚举 l e n len len 的时候只需要枚举 n n n 的因数即可。

2.

匹配可以用哈希来搞,但是如果将相邻的两节依次匹配的话还是很慢,最坏时间复杂度为 O ( n ) O(n) O(n),于是如何进一步优化呢?

仔细观察一下匹配过程:
在这里插入图片描述

每一次匹配都是跟自己右边的匹配,然后我们又知道,hash的匹配无论多长都是 O ( 1 ) O(1) O(1) 的,于是像这样子搞一搞——
在这里插入图片描述

将字符串复制一份,一个掐头,一个去尾,然后直接匹配,效果与两两匹配是一样的,因为现在上下对应的两个就是原来左右相邻的两个。

3.

part 1

经过尝试后,发现用 O ( n ) O(\sqrt n) O(n ) 的方法来枚举因数还是不够优秀的(卡了半天常也过不去,可能是我太蒟了qaq),于是发掘一下更加优秀的做法。

一个数可以写成 a 1 b 1 a 2 b 2 . . . a_1^{b_1}a_2^{b_2}... a1b1a2b2... 的形式,对于每个数,虽然因数可能很多,但是它的质因数个数实际少得可怜,并且每一个数的 a a a 数组和 b b b 数组都是可以预处理的,于是只需要枚举每一个质因数的指数就可以了。

但这种做法只是比 O ( n ) O(\sqrt n) O(n ) 的做法快那么一点,所以总的时间复杂度依然不乐观……

综上,枚举因数似乎不大好做,于是需要换一种做法。


part 2

有一个看似很废的性质:假如长度 x x x 是字符串的一个循环节,那么当 x ∗ k x*k xk 也是总长的一个因数时, x ∗ k x*k xk 也一定是一个循环节。

一开始觉得它很废是因为如果要得到 x ∗ k x*k xk 首先肯定要得到 x x x,但那我都有 x x x 了我还要 x ∗ k x*k xk 有鸟用?

但是换一个角度看这条性质,它其实等价于——假如长度 x x x 不是循环节,那么它的因数就都不是循环节。

于是根据这条性质,就得到了另一种做法,将 l e n len len 的质因子都找出来,假如 l e n / len/ len/某质因子 之后,得到的长度依然是一个循环节,那么 l e n len len 就除以这个因子,否则就不除,因为除了之后得到的那个因数不是循环节,根据上述性质,我们并不能用这个奇怪的因数得到答案。

4.

卡常qwq

最后附上代码,还有些没讲到的细节里面有:

#pragma GCC optimize(3)
#include <cstdio>
#include <cstring>
#define ull unsigned long long
#define jinzhi 315ull
#define mod 2147463647ull
#define R register
#define maxn 500010

int n,m;
char s[maxn];
inline int ch(char x){return x-'a';};
ull hash[maxn],power[maxn];
inline ull get(int l,int r){return (hash[r]-hash[l-1]*power[r-l+2]%mod+mod)%mod;};
//滚动哈希求子串的哈希值
inline bool check(const int x,const int y,int blo){return get(x,y-blo)==get(x+blo,y);}
//看长度blo是不是x~y这个子串的循环节
inline char nc()
{
    static char buf[1000010],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}//读优
void read(int &x)
{
    x=0;char ch=nc();
    while(ch<'0'||ch>'9')ch=nc();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=nc();
}
bool v[maxn];
int prime[maxn],t=0;
void work()//线性筛素数
{
    for(int i=2;i<=maxn-10;i++)
    {
        if(!v[i])prime[++t]=i;
        for(int j=1;j<=t;j++)
        {
            if(prime[j]*i>maxn-10)break;
            v[prime[j]*i]=true;
            if(i%prime[j]==0)break;
        }
    }
}
int minfactor[maxn];
//每一个数的最小质因子,后面分解质因数时用到
void work2()//预处理minfactor
{
    for(int i=1;i<=t;i++)
    {
        for(int j=1;j*prime[i]<=maxn-10;j++)
        if(!minfactor[prime[i]*j])minfactor[prime[i]*j]=prime[i];
    }
}
int a[maxn],st;

int main()
{
    scanf("%d",&n);
    scanf("%s\n",s+1);
    work();
    work2();
    for(R int i=1;i<=n;++i)
    hash[i]=(hash[i-1]*jinzhi%mod+(ull)s[i])%mod;
    power[1]=1;
    for(R int i=2;i<=n;++i)
    power[i]=power[i-1]*jinzhi%mod;
    read(m);
    R int x,y;
    while(m--)
    {
        read(x);read(y);
        int block(y-x+1);st=0;
        while(block!=1)a[++st]=minfactor[block],block/=minfactor[block];//用a存下将block分解质因数后的结果
        block=y-x+1;//记得还原block
        for(int i=1;i<=st;i++)
        if(check(x,y,block/a[i]))block/=a[i];
        printf("%d\n",block);
    }
}
### 关于题目 'A Horrible Poem' 的解析 'A Horrible Poem' 是一道经典的字符串处理问题,主要涉及哈希算法的应用。以下是关于该题目的详细解答: #### 1. 题目描述 给定一个长度为 $n$ 的字符串 $S$ 和若干次查询操作 $(l, r)$,每次查询需要返回子串 $S[l..r]$ 是否可以表示为主串的一个周期。 #### 2. 解决思路 为了高效解决问题,通常采用 **字符串哈希技术** 来预处理字符串并快速计算任意区间的哈希值。具体实现如下: - 使用滚动哈希(Rolling Hash)来存储前缀哈希数组 `prefix_hash`。 - 对于每个询问区间 `[l, r]`,通过比较两个部分的哈希值来判断是否存在周期性关系: - 子串 $S[1 \dots n-r+l]$ 和 $S[r-l+1 \dots n]$ 应具有相同的哈希值[^2]。 ##### 哈希函数定义 假设我们使用双哈希法以减少冲突概率,则对于字符序列 $s_1 s_2 \dots s_n$ 定义其哈希值为: $$ H(i,j) = (\text{Hash}_p(j)-\text{Hash}_p(i-1)\times p^{j-i+1}) \% MOD $$ 其中 $\text{Hash}_p(k)=\sum_{i=1}^k s_i\times p^{k-i}$ 表示到位置 $k$ 的前缀哈希值[^1]。 下面是基于上述理论的具体代码实现: ```python MOD1, MOD2 = int(1e9 + 7), int(1e9 + 9) BASE = 31 def power(base, exp, mod): res = 1 while exp: if exp & 1: res = (res * base) % mod base = (base * base) % mod exp >>= 1 return res def preprocess(s): n = len(s) prefix_hash1, prefix_hash2 = [0]*(n+1), [0]*(n+1) pow_base1, pow_base2 = [1]*(n+1), [1]*(n+1) for i in range(1, n+1): prefix_hash1[i] = (prefix_hash1[i-1]*BASE + ord(s[i-1])) % MOD1 prefix_hash2[i] = (prefix_hash2[i-1]*BASE + ord(s[i-1])) % MOD2 pow_base1[i] = (pow_base1[i-1]*BASE) % MOD1 pow_base2[i] = (pow_base2[i-1]*BASE) % MOD2 return prefix_hash1, prefix_hash2, pow_base1, pow_base2 def get_hash(l, r, phash, pbases, mod): hl = (phash[r+1] - phash[l]*pbases[r-l+1]) % mod return hl if hl >= 0 else hl + mod def solve(): import sys input = sys.stdin.read data = input().split() n = int(data[0]) S = data[1] q = int(data[2]) queries = [] idx = 3 for _ in range(q): l = int(data[idx]); r = int(data[idx+1]) queries.append((l,r)) idx += 2 prefix_hash1, prefix_hash2, pow_base1, pow_base2 = preprocess(S) results = [] for l, r in queries: length = r - l + 1 cycle_length = length // 2 h1_left = get_hash(l-1, l-1+cycle_length-1, prefix_hash1, pow_base1, MOD1) h1_right = get_hash(r-cycle_length+1, r, prefix_hash1, pow_base1, MOD1) h2_left = get_hash(l-1, l-1+cycle_length-1, prefix_hash2, pow_base2, MOD2) h2_right = get_hash(r-cycle_length+1, r, prefix_hash2, pow_base2, MOD2) if h1_left == h1_right and h2_left == h2_right: results.append(length//cycle_length) else: results.append(1) return "\n".join(map(str,results)) print(solve()) ``` 此程序实现了高效的字符串匹配逻辑,并利用双重模数防止碰撞风险。 #### §常见错误分析§ 如果提交失败可能的原因有以下几个方面: 1. 数据范围过大未考虑优化时间复杂度至O(n log n)[^3]; 2. 字符串哈希过程中忽略了取余运算可能导致溢出; 3. 边界条件设置不当比如当$l=r$时特殊处理缺失; --- ####
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值