本来以为只是大整数计算的问题, 用了很笨的方法, 计算出前100000个斐波那契数然后逐一匹配
结果等了半天也没算出来... 后来一查发现到后面会出现10^2W次方的位数, 显然不能用这种方法
后来google了答案, 才想起来这是前缀查找的, 应该用字典树来存储每个数, 而且只需要存储前40位即可.....原先想的太复杂了啊
再把别人AC的思路描述一下把:
整个字典树用数组存储, 每用到(增加)一个结点就在数组中存一个, 这样可以最大限度地节省空间
每个结点内包含10个子节点的下标, 以及一个Res, 存储经过这个结点且序号最小的一条路径(即一个斐波那契数)
首先从头开始逐个计算斐波那契数, 就用F(n) = F(n-1) + F(n-2)的式子, 每个F(N)用高精度类型存储
不过为了节省空间, 可以循环利用, 算完F(N)之后, F(N-2)就不需要再用了, 而且原来的F(N)就变成了F(N-1), F(N-1)变成F(N-2),
因此可以把F(N-2)的内存用来存储F(N), 以此类推
每算出一个F(N), 把它的数值和序号都插入字典树中, 注意存入的时候, 如果要创建新的结点, 就把这个结点的Res置为当前的N(序号)
如果插入的时候深度超过40了就停止插入, 因为输入最多也就到40了
基本上是这样, 下面是仿照别人的代码写的
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <list>
#include <cassert>
#include <iomanip>
using namespace std;
const int MAXN = 100000;
const int MAXBIT= 40;
const int BASE = 100000000;
typedef long long LL;
/*
uva 12504
关键 : 1. 存储->高精度整数( 大于40位的计算 )
2. 查找->字典树( 前缀匹配 )
3. 字典树结点的数据结构 : 只用一个int型数组存储子节点的下标, 外加一个bool标记是否终点即可
注意要将整棵树只存在一个结点数组中, 因为结点过于稀疏,
不可能定义数组长度为10^(树高), 只需【用到一个就增加一个】即可
*/
struct Trie{
int link[10]; // 存储子节点的下标
int Res; // 标记这个数(如果是的话)是第几个斐波那契数
} Tries[40000000]; // Tries[0]为默认结点(根节点)
char buf[100]; // 用来存储把大整数转换成字符串后的字符串
// 定为全局变量是为了方便char数组(字符串)的传递
// (若返回在转换函数内定义的char数组则不可行, 函数内定义的数组在栈中会被回收)
char * IntToStr(int arr[], int len){ // 返回char指针可行, 因为指向的内存在堆中而非此函数的栈中
char * p = buf;
for(int i=len-1, cnt=0; i>=0 && cnt<6 ; cnt++, i--){
*(p++) = (arr[i]/10000000)%10 + '0'; // 最高位
*(p++) = (arr[i]/1000000)%10 + '0';
*(p++) = (arr[i]/100000)%10 + '0';
*(p++) = (arr[i]/10000)%10 + '0';
*(p++) = (arr[i]/1000)%10 + '0';
*(p++) = (arr[i]/100)%10 + '0';
*(p++) = (arr[i]/10)%10 + '0';
*(p++) = (arr[i]/1)%10 + '0';
}
p = buf;
while( *p=='0' ) p++;
return p;
}
void TrieIns(char const * num,int val){
static int TrieNum = 0;
int idx = 0, m = 0, len = strlen(num);
// printf("%5d:%s\n",val,num);
for(int i=0; i<len && i<MAXBIT; i++){
if( Tries[idx].link[num[i]-'0']==0 ){ // 没有创建过的结点
Tries[idx].link[num[i]-'0'] = ++TrieNum;
Tries[TrieNum].Res = val;
// 为什么在这里要填入Res : 因为这道题只要模糊地找出有某个前缀的斐波那契数,
// 并不是要准确地找出斐波那契数的值, 并且模糊找的那个应该是数列中序号最小的
// 因此只需要在第一次到达的结点处填入当前的序号即可
}
idx = Tries[idx].link[num[i]-'0'];
}
}
void Generate(){
int fib[3][100000], len; // 用于计算斐波那契数列(求幂)的F(N-2), F(N-1), F(N)
int a = 0, b = 1, c = 2;
memset(Tries,0,sizeof(Tries));
len = 1;
memset(fib,0,sizeof(fib));
fib[a][0] = fib[b][0] = 1;
TrieIns(IntToStr(fib[a],len),0); // 斐波那契数列下标为0和1的都等于1, 只插入其中一个即可
for(int i=2; i<100000; i++ ){
for(int j=0; j<len; j++){
fib[c][j] = fib[a][j] + fib[b][j];
}
for(int j=0; j<len; j++){
if( fib[c][j]>=BASE ){
fib[c][j+1] += fib[c][j]/BASE;
fib[c][j] %= BASE;
}
}
if( fib[c][len]>0 ) len++;
TrieIns(IntToStr(fib[c],len),i);
a++, b++, c++;
a %= 3;
b %= 3;
c %= 3;
}
return ;
}
int main(){
// freopen("input2.txt","r",stdin);
// freopen("output.txt","w",stdout);
Generate();
char input[MAXBIT+10];
int T,Case = 0;
scanf("%d ",&T);
while( T-- ){
scanf("%s",input);
int i, idx = 0;
for(i=0; i<strlen(input); i++){
if( Tries[idx].link[input[i]-'0']==0 ){ // 没有到达过的结点
break;
}
idx = Tries[idx].link[input[i]-'0'];
}
printf("Case #%d: ",++Case);
if( i<strlen(input) ){
printf("-1");
}else{
printf("%d",Tries[idx].Res);
}
printf("\n");
}
return 0;
}