习题5-15 uva12333 Fibonacci的复仇

  1. 斐波那契数列 : 1,1,2,3,5,8 ......,F(n) = F(n-1) + F(n-2) ,F(0)=F(1)=1
  2. 任务:给出一个斐波那契数的前几个数字(不超过40个数字),找出以它开头的最小的Fibonacci数的序号。如果序号小于100000的Fibonacci数均不符合条件,输出-1.
  3. 坑点:卡时间,首先是10万个数必须都要求出来,才能判断是不是有符合的,然后这题的数据规模是 小于等于 50000个数,每算一个遍历一次是不行的,肯定超时,还有就是计算,完整精确计算是不能的,只能保留一定数量的有效数字。
  4. 自己想不出来,看了看别人的解法,发现都是构造字典树,字典树没学过。虽然看懂了大概,但是我不想用,因为他们都构造了一个400多万元素的数组,粗略估计了一下,大概200MB内存,感觉代价有点高。
  5. 反正开头肯定是打表,10万数组是少不了的。既然主要是查询费时间,那就从查询入手改造。思路是先打表,然后排序,再二分查找位置,然后从这个位置向下搜索符合条件的,从中选一个序号小的,遇到不符合条件的就终止。二分查找时间就很低了,拿自己电脑测试了一下,开头的打表排序也就0.7s左右。
  6. 通俗解释一下:因为字符串也能比较,就把他们类比成数字好了,例如 “aaa”“abc”“abc”“abcd”“c”“ccd”“d”“d”“d”“dff”就可以记作为 1, 5, 5, 6, 7, 8, 9, 12, 12, 12假设我想要搜索前缀为“ab”的,好比在右面寻找数字4,利用二分找到他的位置,“aaa”“ab”“abc”“abc”“abcd”“c”“ccd”“d”“d”“d”“dff”。1, 4, 5, 5, 6, 7, 8, 9, 12, 12, 12,只是找位置而已,并非插入数组中,他排在第0个元素后,所以位置pos=0。然后从位置pos向后搜索(不搜索位置pos)包含此前缀“ab”的,只要遇到一个不包含的,就停止,因为后面的肯定也不包含了,从这些包含前缀的选一个序号小的。如果上来第一个(pos+1)就停下了,肯定是-1的那种情况。
  7. 保存大数字,这个我写的比较丑陋,反正够用,用60位来保存有效数字,还要记录指数,因为指数不一样不好相加,还要移位

代码

#include <iostream>
#include <string>
#include <cstdio>
#include <algorithm>
#define maxn 60
using namespace std;
struct Bigint {
    int bit[maxn] = {1}, index = 0;//由低到高
    void indent(int maxnbit) {
        for(int i = 0; i < maxn - 1; bit[i] = bit[i + 1], i++);
        bit[maxn - 1] = maxnbit;
        index++;
    }
    void operator+=(Bigint& b) {
        if(index < b.index)  indent(0);
        else if(index > b.index)  b.indent(0);
        int c = 0;
        for(int i = 0; i < maxn; i++) {
            int tem = bit[i] + c + b.bit[i];
            bit[i] = tem % 10;
            c = tem / 10;
        }
        if(c == 1)  indent(1);
    }
};
struct Node {
    int fid;
    string s;
    bool operator<(const Node& n2) {
        return s < n2.s;
    }
} arr[100001];//把arr[0]的string设置为最小
inline void getnode(const Bigint& b, Node& node) { //将前n个数字转为string,不够n个按实际算
    static int fid = 0;
    int pos = maxn - 1;
    for(; !b.bit[pos]; pos--);//不要最高位的0
    for(int i = pos, j = 0; i >= 0 && j < 40; i--, j++)
        node.s += char(b.bit[i] + '0');
    node.fid = fid++;
}
int possearch(int ibegin, int iend, const string& str) {
    if(ibegin + 1 >= iend) return ibegin;
    int mid = (ibegin + iend) / 2;
    return str <= arr[mid].s ? possearch(ibegin, mid, str) : possearch(mid, iend, str) ;
}
int main() {
    Bigint a, b;
    for(int i = 1; i <= 100000; a += b, b += a) {
        getnode(a, arr[i++]);
        getnode(b, arr[i++]);
    }
    arr[0].s="000";//便于查找
    sort(arr, arr + 100001);
    int t, ncase = 1;
    string num;
    cin >> t;
    while(t--) {
        cin >> num;
        int pos = possearch(0, 100000, num), n = num.size(), ans = -1;
        for(int i = pos + 1; i < 100001; i++)
            if(arr[i].s.substr(0, n) == num) {
                if(ans == -1 || ans > arr[i].fid)  ans = arr[i].fid;
            } else break;
        printf("Case #%d: %d\n", ncase++, ans);
    }
}




     

2024.5.5更新,偶然又翻到2018年2月这道题,重写了一下,原理不变,增加可读性。

#include <iostream>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int maxn = 60;
using namespace std;
struct FibNum{
	int leading[maxn] = {};	//前60位数字,不够60位的前面补 0
	int exp = 0;		//指数,一个FibNum数值约等于 leading * 10^exp,仅超过60位的大数需要
	FibNum(int val = 0) {	//初始化
		exp = 0;
		leading[maxn - 1] = val;
	}
	//特殊移位运算,右移n位,同时指数+n,注意:仅限右移
	FibNum& shift(int n = 1){
		for(int i = maxn - 1; i >= 0; i--){
			leading[i] = (i-n >= 0 ? leading[i-n] : 0);
		}
		exp += n;
		return *this;
	}
	//重载一下 += 运算符
	FibNum& operator+=(const FibNum& b){
		int i = maxn - 1, j = maxn - 1; // 指数对齐后的 leading 下标
		if(this->exp >= b.exp){
			j -= this->exp - b.exp;
		}else{
			this->shift(b.exp - this->exp);
		}
		int carry = 0;	//进位
		for(; i>=0; i--,j--){
			int x = this->leading[i];
			int y = (j >= 0) ? b.leading[j] : 0;
			int t = x + y + carry;
			this->leading[i] = t % 10;
			carry = t / 10;
		}
		// 若最高位溢出,需要右移
		if(carry > 0){
			this->shift(1);
			this->leading[0] = carry;
		}
		return *this;
	}
};
const int max_nums_len = 41;
struct FibNode{
	char nums[max_nums_len] = {};
	int fib_id = -1;
	bool operator<(const FibNode& b) const{
		return strcmp(nums, b.nums) < 0;
	}
	void set_value(const FibNum& f, int fid = -1){
		fib_id = fid;
		int i = 0, j = 0;
		for(; f.leading[i] == 0; i++); // 忽略前导0
		for(; i<maxn && j<max_nums_len-1; i++){
			nums[j++] = f.leading[i] + '0';
		}
		nums[j] = '\0';
	}
};
const int max_cnt = 100000; //小坑:恰好是第 100000 个fib数时要输出-1
FibNode fib_arr[max_cnt];
void pre_table(){ //打表
	int fid = 0;
	FibNum a(1) , b(1);
	while(fid < max_cnt){
		fib_arr[fid].set_value(a, fid);
		fid++;
		if(fid >= max_cnt)
			break;
		fib_arr[fid].set_value(b, fid);
		fid++;
		a += b;
		b += a;
	}
	sort(fib_arr, fib_arr + max_cnt);
}
int query(const FibNode& q){
	int ret = -1;
	int qlen = strlen(q.nums);
	int first = lower_bound(fib_arr, fib_arr + max_cnt, q) - fib_arr;
	for(int i = first; i < max_cnt; i++){
		if(strncmp(fib_arr[i].nums, q.nums, qlen) == 0){
			ret = ret == -1 ? fib_arr[i].fib_id : min(ret, fib_arr[i].fib_id);
		}else{
			break;
		}
	}
	return ret;
}
int main() {
    pre_table();
    FibNode q;
	int t, ncase = 1;
    cin >> t;
    while(t--) {
        cin >> q.nums;
        printf("Case #%d: %d\n", ncase++, query(q));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值