原文题目链接:
http://projecteuler.net/problem=64
翻译题目链接:
http://pe.spiritzhang.com/index.php/2011-05-11-09-44-54/65-6410000
通过人数:10607
题目分析:
粗略来看,是一个计算并统计计算结果的模型。不过计算部分比较复杂,涉及到开方运算。
最开始,我只是用C语言<math.h>中的sqrt()函数进行的开方运算。但发现结果不对,并且非常慢,经检查发现在计算循环节的时候有些循环节算的肥城大,成千上万。
后来注意到是sqrt()函数的精度问题。每一次连分展开逼近的精度是非常可观的。当连分展开项超过十几项的时候,sqrt()的精度已经达不到要求了,于是理论上是找不到循环节的。但由于判断是否相等的函数只是返回差值是否小于0.00001,于是还是有可能在几万次之后蒙上的。这也就是为什么我最开始的方法会导致结果不对并且非常慢了。
于是解题的关键就变为了求高精度开方并且进行高精度计算来求循环节的长度了。
考虑到C语言自己处理高精度非常麻烦,java有现成的高精度类。于是换用java语言。
解题过程(代码仅供参考,因为偷懒,代码风格什么的实在不好意思...):
零、先上一下完整代码:
package test;
import java.math.*;
public class Main
{
public static void main( String args[] )
{
BigDecimal Zero = new BigDecimal("0.0000000000000001");
BigDecimal N0 = new BigDecimal(0);
BigDecimal N1 = new BigDecimal(1);
BigDecimal N2 = new BigDecimal(2);
BigDecimal N = new BigDecimal(1);
int ans[] = new int[10000];
for (int i=2;i<=10000;i++)
{
BigDecimal Ni = new BigDecimal(i);
while(true)
{
BigDecimal NN = N.multiply(N);
NN = NN.add(Ni);
NN = NN.divide(N2);
NN = NN.divide(N, 500, BigDecimal.ROUND_FLOOR);
if (NN.equals(N))
break;
N = NN;
}
BigDecimal N_Int = N.setScale(0, BigDecimal.ROUND_FLOOR);
BigDecimal N_Dec = N.subtract(N_Int);
BigDecimal N_f = N_Int.multiply(N_Int);
if (N_f.equals(Ni))
continue;
BigDecimal N_t = N_Dec.add(N0);
int temp_n = 0;
while(true)
{
temp_n++;
N_t = N1.divide(N_t, 500, BigDecimal.ROUND_FLOOR);
N_t = N_t.subtract(N_t.setScale(0, BigDecimal.ROUND_FLOOR));
BigDecimal d = N_t.subtract(N_Dec).abs();
if (d.subtract(Zero).signum() < 0)
{
ans[i] = temp_n;
break;
}
}
}
int out = 0;
for (int i=2;i<10000;i++)
if (ans[i]%2==1)
out++;
System.out.print(out);
}
}
一、求开方(使用牛顿迭代法):
利用百度百科上的公式,求得算a开方的迭代公式:x[n+1]=(x[n]*x[n]+a)/(2x[n])
在计算过程中保留500位精度(最开始我尝试的是200位的精度,但发现算到7606的时候还是精度不足...于是一怒之下改到了500位)
while(true)
{
BigDecimal NN = N.multiply(N);
NN = NN.add(Ni);
NN = NN.divide(N2);
NN = NN.divide(N, 500, BigDecimal.ROUND_FLOOR);
if (NN.equals(N))
break;
N = NN;
}
这段代码中,N2储存的是
BigDecimal类型的常值2,Ni储存的是BigDecimal类型的常值被开方数i,N的初始值是上次计算的sqrt(i-1),然后进行迭代。
二、求开方的连分数展开循环节
这段完全照搬题目的做法就好了。
while(true)
{
temp_n++;
N_t = N1.divide(N_t, 500, BigDecimal.ROUND_FLOOR);
N_t = N_t.subtract(N_t.setScale(0, BigDecimal.ROUND_FLOOR));
BigDecimal d = N_t.subtract(N_Dec).abs();
if (d.subtract(Zero).signum() < 0)
{
ans[i] = temp_n;
break;
}
}
这段代码中,N_t的初始值是
BigDecimal类型的sqrt(i)的小数部分,N1存的是
BigDecimal类型的常值1,N_Dec存的是BigDecimal类型的常值sqrt(i)的小数部分,Zero存的是BigDecimal类型的常值一个非常小的正数。temp_n计算循环节长度,初始值为0。
三、统计并输出结果
int out = 0;
for (int i=2;i<10000;i++)
if (ans[i]%2==1)
out++;
System.out.print(out);
终于到了最好做的部分了~O(∩_∩)O~
结果:1322
代码运行时间:若干秒...但应该不到1min...毕竟还是进行了10000次精度500位的开方运算的~并且还是用的java
只是不太清楚为什么这题的通过人数这么高...看了一些python和c的通过代码,发现很多都没怎么考虑精度问题...是我在求循环节的时候选用了不好的方法么?纠结ing....&&不喜欢读别人的代码...%>_<%...
以上只是我做题时的解法。
如果有更好的解法、更好的思路,欢迎评论讨论~O(∩_∩)O~