NYOJ 314 解题报告
作者:互联网
斐波那契数列四吧
时间限制: 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”
每组数据输出后换行。
样例输入
1 1000000样例输出
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;
}
标签:20,gcd,Extend,int,NYOJ,long,314,解题,数列 来源: https://blog.51cto.com/liulizhi1996/3035757