【题目链接】

ybt 1457:Power Strings

【题目考点】

1. 字符串哈希

相关知识见:【模板:字符串哈希】LOJ #103. 子串查找

2. KMP算法

相关知识见:[【模板:KMP算法】]

【解题思路】

对于每个给定的字符串,求该字符串最小循环节的循环次数。
以下会使用python字符串切片写法来表示字符串的子串。

解法1:字符串哈希

字符串 s s s的循环节的长度一定是 s s s的长度 n n n的约数。
从小到大枚举字符串 s s s的长度 n n n的约数 l l l,将 s s s分为多个长为 l l l的子串,判断每个子串是否相同。
构造将字符串 s s s当做 b a s e base base进制数,求出该数的数值作为哈希值,哈希值使用unsigned类型保存,进行自然溢出。
求出字符串 s s s的哈希数组 h h h h i h_i hi表示 s s s的前 i i i个字符构成的子串,即 s [ : i ] s[:i] s[:i]的哈希值,满足递推式 h i = h i − 1 b a s e + c i ] h_i=h_{i-1}base+c_{i]} hi=hi1base+ci] c i c_i ci s s s的第 i i i个字符转为的数值),可以递推求出。
那么 s s s的循环节为 s s s l l l个字符构成的子串 s [ : l ] s[:l] s[:l],该子串的哈希值为 h l h_l hl
根据字符串哈希算法, s s s从下标 i i i开始长为 l l l的子串 s [ i : i + l ] s[i:i+l] s[i:i+l]的哈希值为 h i + l − h i b a s e l h_{i+l}-h_ibase^l hi+lhibasel
如果字符串 s s s的每个长为 l l l的子串的哈希值都和前 l l l个字符构成的子串 s [ : l ] s[:l] s[:l]的哈希值相同,那么 l l l就是该字符串的最短循环节, n / l n/l n/l为最短循环节出现的次数。

初始化哈希数组的时间复杂度为 O ( n ) O(n) O(n)
对于每个 n n n的约数 l l l,需要最多循环 n / l n/l n/l次来判断长度 l l l是否为字符串 s s s的循环节的长度。每次循环可以以 O ( 1 ) O(1) O(1)时间复杂度比较两个字符串的哈希值是否相同。不考虑出现哈希冲突的情况下,哈希值相同可以近似理解为原字符串相同。
最大总循环次数为
∑ l ∣ n n l = n ∑ l ∣ n 1 l < n ∑ l = 1 n 1 l \sum_{l|n}\frac{n}{l}=n\sum_{l|n}\frac{1}{l}<n\sum_{l=1}^n\frac{1}{l} lnln=nlnl1<nl=1nl1
∑ l = 1 n 1 l < ∫ x = 1 n 1 x d x = ln ⁡ x ∣ 1 n = ln ⁡ n \sum_{l=1}^n\frac{1}{l}<\int_{x=1}^n\frac{1}{x}dx=\ln x|^n_1=\ln n l=1nl1<x=1nx1dx=lnx1n=lnn
所以: ∑ l ∣ n n l < n ln ⁡ n \sum_{l|n}\frac{n}{l}<n\ln n lnln<nlnn
其时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn) n n n最大为 10 6 10^6 106,如果是一组数据,该算法可以保证通过此题。
多组数据问题,如果没有明确给出数据组数,可以认为数据组数小于10组。
实际执行过程中, l l l从小到大的循环次数以及对所有长为 n / l n/l n/l子串判断其是否等于 s [ : l ] s[:l] s[:l]的过程的循环次数都不会达到最大,因此实际的运算次数会比估算的数值更小。该算法最终通过了此题。

解法2:KMP算法

已知长为 n n n的字符串 s s s的最短循环节长度为 n − n e x t [ n ] n-next[n] nnext[n]
其中 n e x t next next数组为字符串 s s s的前缀数组, n e x t [ i ] next[i] next[i]表示 s [ : i ] s[:i] s[:i]的最长相等前后缀的长度,可以通过KMP算法求出。
如果字符串由若干完整的循环节构成,必须满足 n n n是最短循环节长度 n − n e x t [ n ] n-next[n] nnext[n]的倍数。循环次数为: n / ( n − n e x t [ n ] ) n/(n-next[n]) n/(nnext[n])

【题解代码】

解法1:字符串哈希

#include <bits/stdc++.h>
using namespace std;
#define N 1000005
string s;
unsigned bp[N], h[N], base = 31;
void initBp()
{
	bp[0] = 1;
	for(int i = 1; i < N; ++i)
		bp[i] = bp[i-1]*base;
}
bool check(int l, int len)//判断s的循环节长度能否为l 
{
	for(int i = l; i < len; i += l)//看s[i:i+l]是否与s[:l]一样 
		if(h[l] != h[i+l]-h[i]*bp[l]) 
			return false;
	return true; 
}
int main()
{
	initBp();
	while(cin >> s && s != ".")
	{
		int len = s.length();
		for(int i = 1; i <= len; ++i)
			h[i] = base*h[i-1]+s[i-1]-'A'+1;
		for(int l = 1; l <= len; ++l) if(len%l == 0 && check(l, len))//l:子串长度,是len的因数 
		{
			cout << len/l << endl;//输出循环节的个数 
			break;
		}
	}
	return 0;
}
解法2:KMP算法
#include <bits/stdc++.h>
using namespace std;
#define N 1000005 
int Next[N];
int main()
{
	string s;
	while(cin >> s && s != ".")
	{
		int i = 0, j = Next[0] = -1, ls = s.length();
		while(i < ls)
		{
			if(j == -1 || s[i] == s[j])
				Next[++i] = ++j;
			else
				j = Next[j];
		}
		if(ls%(ls-Next[ls]) == 0)//如果是完整的由循环节构成的字符串 
			cout << ls/(ls-Next[ls]) << endl;
		else
			cout << 1 << endl;
	}
	return 0;
}
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐