题目地址
https://uva.onlinejudge.org/index.php?option=onlinejudge&page=show_problem&problem=2544
题意
判定一个循环节中最大的数是多少
分析
首先,题目中的数据范围太大,不能用vis数组来存下访问过的数,一个方法是可以使用set,不过set会带来
O(logn)
的额外复杂度,由于题目中没有让我们存在下所有出现的数,只是记录出现最大的数,所以有没有时间复杂度
O(n)
以及空间复杂度
O(1)
的算法呢?
大家都清楚的应该就是Floyd判圈算法了,网上也有相应性质的证明,但是比Floyd更快的Brent算法网上好像没有相关的中文介绍,所以这里打算基于自己的理解介绍下这两种算法。
Floyd判圈算法
让兔子每个单位时间前进2步,乌龟每个单位时间前进1步,在 O(n) 的时间内,兔子就会超过乌龟一圈,然后在接下来的 O(2n) 的时间内,兔子和乌龟一定会在某个点相遇,此时循环节发现了。
求环的长度
保持兔子不动(其实哪个指针不动,哪个指针动都差不多了),让乌龟每个单位时间前进1步,当兔子与乌龟再次相遇时,花费的时间就是循环节的长度,此结论显然
求环的起点
让兔子回到起点s,乌龟停留在相遇点m,首先由之前的操作我们知道s经过长度为
2n
和
n
的路程,都会到达m点,那么
假设m点距环的起点的距离为
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, k;
LL bits[11];
int nxt(LL k) {
k = k * k;
while (k >= bits[n]) k /= 10;
return k;
}
int main() {
bits[0] = 1;
for (int i = 1; i < 11; ++i) bits[i] = bits[i - 1] * 10;
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
int ans = k, k1 = k, k2 = k;
do {
k1 = nxt(k1);
k2 = nxt(k2); if (ans < k2) ans = k2;
k2 = nxt(k2); if (ans < k2) ans = k2;
} while (k1 != k2);
printf("%d\n", ans);
}
}
Brent判圈算法
这是一个倍增算法,让乌龟保持不动,兔子走 2i 步,看这个过程中龟兔有没有相遇,没有的话,让乌龟的位置变成兔子的位置(如果乌龟位置一直不变,它可能不会进入环中),让兔子走 2i+1 步,看看会不会相遇,如此循环。这个算法也是 O(n) 的,但是它会比Floyd表现的更好,且Floyd是这个算法最差时的表现。
求环的长度
因为乌龟一直处在兔子更改步长上限时的位置,所以更改步长后,兔子走了几步与乌龟相遇,环的长度就是几步(就是代码中的steps_taken
变量)
求环的起点
Floyd判圈算法利用了乌龟和兔子的距离是环长整数倍的性质来求出起点,所以可以让乌龟回到起点,兔子回到距离起点
c
(
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n, k;
LL bits[11];
int nxt(LL k) {
k = k * k;
while (k >= bits[n]) k /= 10;
return k;
}
int main() {
bits[0] = 1;
for (int i = 1; i < 11; ++i) bits[i] = bits[i - 1] * 10;
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
int ans = k, k1 = k, k2 = k;
int steps_taken = 0, step_limit = 2;
while (true) {
k2 = nxt(k2);
if (k2 > ans) ans = k2;
if (k1 == k2) break;
++steps_taken;
if (steps_taken == step_limit) {
steps_taken = 0;
step_limit *= 2;
k1 = k2;
}
}
printf("%d\n", ans);
}
}