Life Goes On

まあまあだけど楽しんでる方です

66問目

http://projecteuler.net/index.php?section=problems&id=66
不定方程式 x2 - Dy2 = 1 について、D=13 のとき x の最小値は 6492 - 13×1802 = 1 となる。Dが平方数のとき、自然数の範囲では解がないと想定されている。D ≦ 7 で平方数でないDに対してxの最小値を考えると、D=5 のときに最大値9が得られる。D ≦ 1000 としたとき、xが最大となるようなDの値を求める。
1000以下ならbrute forceで行けるだろうとか思ったら、甘かった。題意の方程式はペル方程式というらしく、〜プログラム@Bal4u〜: ペル方程式の最小解 pellEquとかペル方程式の解の列挙方法 - まめめもとかペル方程式の解の構成とか見て、漸化式を立てて解きました。Webを探せば、答えそのものも出てきます。
基本的には上述のリンク先に従って組めばいいのですが、このコード特有の問題として、13, 14行目があります。このように as', xs' を明示的に定義しないと、リストだと判断されずに毎回関数の値を求めてしまい、メモ化が機能しないのです。

import Data.List

main = print $ euler066 1000

euler066 :: Integer -> Integer
euler066 m = snd $ maximum
    [ (last $ xs i, i) | i <- [1..m] \\ takeWhile (<= m) [ n^2 | n <- [1..] ] ]

xs :: Integer -> [Integer]
xs n = 1 : (head as') : zipWith3 (\ a x1 x0 -> a * x1 + x0) (tail as') (tail xs') xs'
    where as' = as n
          xs' = xs n

as :: Integer -> [Integer]
as n = (\ (s, t, a) -> a) $ unzip3 $ take (index * ((mod index 2) + 1)) rems'
    where index = (findIndices (\ (s, t, a) -> t == 1) rems') !! 1
          rems' = rems n

rems :: Integer -> [(Integer, Integer, Integer)]
rems n = iterate next (0, 1, r)
    where r = floor $ sqrt $ fromIntegral n
          next (s, t, a) = let s' = a * t - s
                               t' = div (n - s' ^ 2) t
                               a' = div (r + s') t'
                           in (s', t', a')

コメントで添削していただいたので、修正版を。主なポイントは↓です。

  • (s, t, a) を (s, (t, a)) とすることで、不要な s を無視。
  • index が偶数なら index まで、奇数なら index*2 までというルールを lcm 2 index で表現。
  • x1 と x0 を (x1, x0) とセットで扱うことで、zipWith3 を scanl で簡潔に表現できる。さらに最後の要素しか見ていないので実は foldl でよい。

変なメモ化は要らないし、速度は当社比4倍です。

import Data.List

main = print $ euler066 1000

euler066 :: Integer -> Integer
euler066 m = snd $ maximum [ (f i, i) 
    | i <- takeWhile (<= m) $ concat [ [n^2+1..(n+1)^2-1] | n <- [1..] ] ]

f :: Integer -> Integer
f n = fst $ foldl (\ (x1, x0) a  -> (a * x1 + x0, x1)) (1, 0) $ as n

as :: Integer -> [Integer]
as n = snd $ unzip $ take (lcm 2 index) $ rems n
    where index = (!! 1) $ findIndices ((== 1) . fst) $ rems n

rems :: Integer -> [(Integer, Integer)]
rems n = snd $ unzip $ iterate next (0, (1, r))
    where r = floor $ sqrt $ fromIntegral n
          next (s, (t, a)) = let s' = a * t - s
                                 t' = div (n - s' ^ 2) t
                                 a' = div (r + s') t'
                             in (s', (t', a'))