UVA 11549 Calculator Conundrum Floyd判圈算法 Brent判圈算法 相关性质及证明

题目地址

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点,那么n是循环节长度 c 的整数倍。
假设m点距环的起点的距离为p,则起点s到环起点t的距离是 k1cp ,相遇点m到环起点t的距离是 cp ,我们让兔子和乌龟都以单位时间一步的速度前进,显然可以看出,当兔子快要进入循环节时离环起点t的距离是 cp ,同时乌龟离环起点t的距离也是 cp ,则两者的相遇点就是环的起点s

代码

#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 c指环的长度)处,然后接下来的步骤和Floyd一样。

代码

#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);
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值