NYOJ 314 解题报告

来源:互联网 发布:c语言temp 编辑:程序博客网 时间:2024/06/05 10:53

斐波那契数列四吧

时间限制:3000 ms  |  内存限制:65535 KB
难度:2
描述

斐波那契数列为:0,1,1,2,3,5,8,13....,常规递推公式为f(n)=f(n-1)+f(n-2);

但这里不一样的是,我们的前两个数字不一定是0,1;

也就是说,斐波那契数列的前两个数字是随机数(保证是非负整数)。下面我们开始吧,我会告诉你斐波那契数列的第20项的值m,请你判断是否会有这样的数列存在。

对了,你要保证数列的每一项都是整数哦

输入
第一行呢,按照惯例我们输入一个数字N,表示测试数据组数。
接下来肯定就有N行了。。
每行包含一个整数m(m<10000000),表示数列的第20项的值。
输出
如果存在这样的数列呢,输出该数列的第一项和第二项的值
else 输出“No answer”
每组数据输出后换行。
样例输入
11000000
样例输出
154 144

       这里有一点需要注意,f(1)=1而非题目中的那个0,因为按题目的意思,f(0)=0。

       首先根据斐波那契数列的递推公式从F(20)开始倒推,F(20)=F(19)+F(18),而F(19)=F(18)+F(17)F(18)=F(17)+F(16),得出F(20)=3F(17)+2F(16),然后一直迭代下去:F(20)=8F(15)+5F(14) F(20)=21F(13)+13F(12)F(20)=55F(11)+34F(10)F(20)=144F(9)+89F(8)F(20)=377F(7)+233F(6)F(20)=987F(5)+610F(4)F(20)=2584F(3)+1597F(2),有没有发现前面的系数也是斐波那契数列的项呢?最后一步,由F(3)=F(2)+F(1)得F(20)=4181F(2)+2584F(1)。这个式子能够帮助我们由第20项推出第1、2两项。

       所以,这道题归根结底就是求解丢番图方程1597x+2584y=f(20)的最小正整数解。这里,gcd(1597, 2584)=1一定能整除f(20),即1 | f(20)。我是这样思考的,首先根据扩展的欧几里得算法求得这个方程的一组特解。

       扩展的欧几里得算法是这样的,找到一组整数s, t使得a*s+b*t=c。实现它是使用一个递归。递归的终止条件是gcd(a,b)*1+b*0=gcd(a,b)。因此需要不停地使用欧几里得算法的步骤来找出s和t前面的一系列系数。先看一下代码:

long long Extend_Euclid(long long *s,long long *t,long long a,long long b){if(b==0){*s=1;*t=0;return a;}long long ans=Extend_Euclid(s,t,b,a%b);long long temp=(*s);*s=*t;*t=temp-(a/b)*(*t);return ans;}

        函数的返回值是s前面的系数。

        而后面的几句赋值语句的意思是,将后一步得出的t值赋值给s,再将后一步得出的s和t得到的s-[a/b]*t赋值个t。这是为什么呢?因为gcd(a, b)=s*rj+t*r(j-1),而欧几里得算法里rj=r(j-2)-r(j-1)*q(j-1),代入有gcd(a, b)=(t-s*q(j-1))*r(j-1)+s*r(j-2)。最后r0=a,r1=b。

        但是现在得到的s和t值又可能是有负数的,所以还要根据通解x=x0+(b/gcd(a, b))*n,y=y0-(a/gcd(a, b))*n一步步寻找使得x, y均大于0的一组解,而且这个一定是最小的正整数解。如果找不到,就表明No Answer。

        最终代码如下:

#include <stdio.h>long long Extend_Euclid(long long *s,long long *t,long long a,long long b){if(b==0){*s=1;*t=0;return a;}long long ans=Extend_Euclid(s,t,b,a%b);long long temp=(*s);*s=*t;*t=temp-(a/b)*(*t);return ans;}int main(){int N;scanf("%d",&N);long long s,t;Extend_Euclid(&s,&t,2584,4181);while(N--){long long m,x,y;int flag=0;scanf("%lld",&m);x=m*s,y=m*t;while(x<0||y<0)        {            if(x<0)            {                x=x+4181;                y=y-2584;                if(x>=0&&y<0)                {                    flag=1;                    printf("No answer\n");                    break;                }            }            else            {                x=x-4181;                y=y+2584;                if(y>=0&&x<0)                {                    flag=1;                    printf("No answer\n");                    break;                }            }        }        if(flag==0)            printf("%lld %lld\n",x,y);}return 0;}

         再来看看标程,简单粗暴,直接上枚举。而且在某些情况下,直接枚举比我的程序运行时间更短!

 #include <stdio.h>int main(){int m,n;scanf("%d",&m);while(m--){scanf("%d",&n);int flag=0;for(int i=0;i*4181<=n;++i){if((n-i*4181)%2584==0)  {printf("%d %d\n",(n-i*4181)/2584,i);flag=1;break;}}if(flag==0)  printf("No answer\n");}return 0;}        



0 0
原创粉丝点击