HDU 4099 Revenge of Fibonacci(高精度加法+字典树Trie)
http://acm.hdu.edu.cn/showproblem.php?pid=4099
题意:
给你一个数,这个数是斐波那契数列中的一个数的前缀,要你找出满足这个前缀的最小下标.且这个下标如果查过了10W,还没有符合要求的数,就输出-1.且输入数不会查过40位,且没有前导0。
分析:
直觉的思路是:将第0到第10W个斐波那契数的前40位都保存为字符串构建字典树。然后对于每个输入串判断是否在该字典树即可。
但是:第10W个斐波那契数大概有2W位整数,我们不可能把所有10W个斐波那契数都算出来(因为超长整数加法太慢)。
所以:我们采取的策略是对于每个斐波那契数我们只保存它的前60位(虽然我们需要前40位),然后后一个数在前两个数的前60位基础上算出。
这种方法是有问题的(但是出现问题的概率很低很低).因为它舍弃了60位后的所有位,如果这后面的位有进位呢?比如假设我们要取前4位,现在我们保存前6位:
11114455555
11115555555
没舍弃前6位之后的数值时:前4位的结果是2223
如果舍弃了前6位后的数值,前4位相加的结果是2222.
所以上面方法是有问题的,但是我们例子中要的是前40位,我们取前60位,只要我们第41位-60位有两个对应位相加有进位,就可以无视60-1000位的进位.这里由于我们就算的数据特殊40-60位肯定是有进位的.
注意:我们程序代码中的实现是,只要第二个数的位数一超过60位(正好61位)我们就同时截断第一个和第二个数的最后一位(最低位),可能就变成了59+60位或60+60位的情况了.所以这个时候我们只需要把这两个数从低位(字符数组的最后一位)一只加到高位即可。
然后对于每个输入的字符串s,我们在Trie树中找与之对应的数下标即可.注意:如果我们在Trie没有找到包含s的串,那就输出-1,如果我们找到了以s为前缀的串,我们就继续向下走到底,找出所有以s为前缀的单词的斐波那契下标,然后输出最小的那个下标即可。
AC代码:(如果把代码中的len2>60 改成len2>45 就会WA 证明了我的论点)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char temp[100];//中间结果
void add(char *a,char *b,char *c)
{
int len1=strlen(a);
int len2=strlen(b);
int i=len1-1,j=len2-1;
int carry=0;//进位
int k=0;//中间结果temp的位数
while(i>=0||j>=0)
{
int x,y,z;
if(i<0) x=0;
else x=a[i]-'0';
if(j<0) y=0;
else y=b[j]-'0';
z=x+y+carry;
temp[k++]=z%10+'0';
carry=z/10;
i--;
j--;
}
if(carry) temp[k++]=carry+'0';//别忘了这个进位
for(int i=0;i<k;i++)c[i]=temp[k-1-i];
c[k]=0;
}
struct trie
{
trie *next[10];
int v;//root节点的v始终为-1,其他节点保存的是经过该节点的最小id号
void init()
{
v=-1;
for(int i=0;i<10;i++)
next[i]=NULL;
}
};
trie *root;
void insert(char *s,int val)
{
int n=strlen(s);
trie *p=root,*q;
for(int i=0;i<n && i<40;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
q=(trie*)malloc(sizeof(trie));
q->init();
p->next[id]=q;
}
p=p->next[id];
if(p->v<0)
p->v=val;
}
}
int find(char *s)
{
int n =strlen(s);
trie *p=root,*q;
for(int i=0;i<n;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
return -1;
}
p=p->next[id];
}
return p->v;
}
char str[3][100];
int main()
{
root = (trie*)malloc(sizeof(trie));
root->init();
str[0][0]='1';
str[0][1]=0;
insert(str[0],0);
str[1][0]='1';
str[1][1]=0;
for(int i=2;i<100000;i++)
{
int len1=strlen(str[0]);
int len2=strlen(str[1]);
if(len2>60)
{
str[0][len1-1]=0;
str[1][len2-1]=0;
}
add(str[0],str[1],str[2]);
insert(str[2],i);
strcpy(str[0],str[1]);
strcpy(str[1],str[2]);
}
int T;
scanf("%d",&T);
for(int kase=1;kase<=T;kase++)
{
scanf("%s",str[0]);
int ans=find(str[0]);
printf("Case #%d: %d\n",kase,ans);
}
return 0;
}