UVA 12333 - Revenge of Fibonacci (斐波那契的复仇) by SuCicada

习题5-15 Fibonacci的复仇(Revenge of Fibonacci, ACM/ICPC Shanghai 2011,
UVa12333)
Fibonacci数的定义为:F(0)=F(1)=1,然后从F(2)开始,F(i)=F(i-1)+F(i-2)。例如,前10
项Fibonacci数分别为1, 1, 2, 3, 5, 8, 13, 21, 34, 55……
有一天晚上,你梦到了Fibonacci,它告诉你一个有趣的Fibonacci数。醒来以后,你只记
得了它的开头几个数字。你的任务是找出以它开头的最小Fibonacci数的序号。例如以12开头
的最小Fibonacci数是F(25)。输入不超过40个数字,输出满足条件的序号。
如果序号小于100000的Fibonacci数均不满足条件,输出-1。
提示:本题有一定效率要求。如果高精度代码比较慢,可能会超时。

原题链接

合适的思路

首先想我们该怎么样能匹配前缀,首先我们不知道完整的数字是什么,所以我们要先得到符合要求的数字数据集。
然后将这些数据集放入字典树
所以我们要做的就是

  1. 使用大数加法技巧,计算前100000个fibonacci
  2. 将每一个fibo数存入字典树中

大数加法

首先先做大数加法,使用字符串存储数字,两个字符串(数字)从最后一位即个位开始一位一位加。
问题在于:

  1. 100000个fibo数字,到后期位数是相当恐怖的,测试时发现第10万个数字有足足2万多位。这会造成在运算时时间和空间的非常糟糕的消耗。
    所以我们就可以遵从题意,只关心前40位,我们只截取每个数字的前面一部分做计算。这就要引出第2个和第3个问题。
  2. 如果我们使用40位,不论后面多少位,就把第40位当作个位。这会产生一个误差的问题。 比如两个数字 11001 和88999,如果我们只取他们的前2位算出来下一个数的前2位是99,但是实际上下一个数的前2位是10。这就是误差。
  3. 在解决第1个问题的时候,我们需要考虑数字进位的情况,因为我们只能看到数字的前一部分,不知道两个相加的数字是否位数相等,比如1234和345相加,假设取前2位,那么我们只能看到12和34,这样直接相加是不对的。

解决

  1. 问题:3:先解决位数问题,我们可以将位数记录在字符串中,比如加入一个最后一位专门用来放位数。但是这样我们只能放256个无符号数字(因为使用char型元素存储)。那么换个思路,因为我们的问题在于两个数字位数不等,而不是位数究竟多少,所以可以只记录位数的奇偶即可。
    (另:其他办法,因为是Fibo数,位数不等的情况下,后一个数是大的数字,位数肯定多,而且第一位肯定是1,可以用这些关键点来解决)
  2. 问题:2:为了解决误差问题,我们就不能只选取前40位,那么应该选几位呢,经过测试发现最小能保证前40位没有问题的位数是52,测试方法见附录
    不过要是不能测试的时候可以以10位为单位扩大范围,反正50和60位的差距远比50和2万的差距小。

字典树

每一个结点中存有

  • 从根节点到此结点的最小的Fibo序数,即最小前缀数字。
  • 一个map,存有下一位的数字到下一位所在的结点的位置映射关系。

结点之间的关系如下图
在这里插入图片描述
在存储的时候,存入的字符串在经过每一个结点的时候,都要比一下这个结点中存储的minIndex(FIbo最小前缀序号),并将其替换成两者中更小的。
这样就能保证按照字典树的脉络寻找,寻找到的结点中的序号,一定是前缀数所在的最小的Fibo序号。

注意点

不知道现在的UVA 编译器是怎么回事,我定义的 int型返回值函数没有给返回值,返回判我 Runtime error。我一开始压根不明觉厉,一行一行注释,然后一次次提交看看哪部分代码没有才会不报 re,以此来判断问题出在哪部分。光是这样就提交了十几次。
我愿称之为绝活。
在 线 O J ,现 场 调 试。

代码

#include<iostream>
#include<map>
#include<cmath>
#include<vector>
#include<cstdio>
using namespace std;

class Site{
        
public:
    int minIndex; // 到目前这个结点为止的最小序数
    map<char,Site*> next; // 下一个位们的 位对应结点
};

Site root;
int limitSite = 52; // 选取的Fibo数的前置位数数,这个数能保证前40位的准确

void putTree
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值