M02406:字符串乘方
Kmp, http://cs101.openjudge.cn/practice/02406/
给定两个字符串a和b,我们定义ab为他们的连接。例如,如果a=”abc” 而b=”def”, 则ab=”abcdef”。 如果我们将连接考虑成乘法,一个非负整数的乘方将用一种通常的方式定义:a^0=””(空字符串),a^(n+1)=a*(a^n)。
输入
每一个测试样例是一行可打印的字符作为输入,用s表示。s的长度至少为1,且不会超过一百万。最后的测试样例后面将是一个点号作为一行。
输出
对于每一个s,你应该打印最大的n,使得存在一个a,让s=a^n
样例输入
abcd
aaaa
ababab
.样例输出
1
4
3提示
本问题输入量很大,请用scanf代替cin,从而避免超时。
来源
Waterloo local 2002.07.01
Ac_kmp.py, 2426ms
'''
使用KMP算法的部分知识,当字符串的长度能被提取的"base字符串"的长度整除时,
即可判断s可以被表示为a^n的形式,此时的n就是s的长度除以"base字符串"的长度。
'''
import sys
while True:
s = sys.stdin.readline().strip()
if s == '.':
break
n = len(s)
next = [0] * len(s)
j = 0
for i in range(1, n):
while j > 0 and s[i] != s[j]:
j = next[j - 1]
if s[i] == s[j]:
j += 1
next[i] = j
p = len(s) - next[-1]
if n % p == 0:
print(n // p)
else:
print(1)Ac.py
from math import sqrt
while True:
s = input()
if s == '.':
break
judge = True
res = 1
for i in range(1, int(sqrt(len(s)))+1):
n = len(s)
if n % i != 0:
continue
if s == s[:i]*(n//i):
print(n//i)
judge = False
break
if s == s[:(n//i)]*i:
res = i
if judge:
print(res)Q: 两个代码都可以AC。时间复杂度分别是多少?为什么KMP反倒慢?
这是一个非常好的问题。简单来说,只循环到
是为了利用因数的对称性进行性能优化。 如果循环到
,算法也是正确的,但对于 这样的数据,循环次数会从 1000次 激增到 500,000次,这在 Python 中是巨大的性能差距。 以下是详细解释:
1. 数学原理:因数是成对出现的
对于任何整数
,如果 是 的因子,那么 一定也是 的因子。 这两个因子一定分布在 的两侧(或者相等)。 例如
, 。因子对如下:
<-- 分界线 (重复了,和 是一样的) (重复了) - ...
结论: 只要遍历
到 ,就可以通过计算 和 得到所有的因子,完全不需要遍历到 。 2. 代码如何利用这一点?
代码在一次循环
i中,实际上做了两次检查,分别对应这“一对”因子:python# 假设 N = 100, i = 2 (循环还在很小的范围内) # 检查 1:把 i 当作“子串长度” # 对应因子对中的 (2, 50) -> 长度为 2,重复 50 次 if s == s[:i]*(n//i): print(n//i) # 找到了最大的 n,直接结束 # 检查 2:把 n//i 当作“子串长度” # 对应因子对中的 (50, 2) -> 长度为 50,重复 2 次 if s == s[:(n//i)]*i: res = i # 记录下这个较小的 n
- 如果不写成
,而是写成 ,那么你需要遍历到 时才能检查到“长度为50”的情况。 - 利用
写法,当 时,就顺便通过 n//i把的情况也检查了。 3. 性能对比
题目限制
最大可达 (一百万)。
如果循环到
:
- Python 的
for循环需要迭代 500,000 次。- 即便很多次
continue跳过了,光是循环本身的开销就足以让 Python 超时(Python 的循环非常慢)。如果循环到
:
- 循环只需要迭代 1,000 次。
- 500,000 vs 1,000,这是 500倍 的差距。
总结
这就是算法竞赛中常用的数论优化。通过只枚举到平方根,同时处理
和 ,可以在不遗漏任何解的情况下,将时间复杂度从线性的 降低到 。