如何让这个 Haskell 程序运行得更快

时间:2021-06-19 07:52:02

标签: haskell primes factors factorization

所以我一直在尝试通过解决 Codeforce 上的一些问题来学习 Haskell。
尽管我认为我的时间复杂度是最佳的,但我还是得到了很多 TLE(超出时间限制)。

我的问题是:是我编写这个程序的方式让它变慢了吗?

例如,这里是 problem

基本上答案是为给定的 an 找到 n ,其中 an = 2*an-1 + D(n)D(n) = nn-1 之间的除数数之差。

(更新:n 的上限为 106)。

下面是我的程序。

import qualified Data.Map.Strict as Map

main = do t <- read <$> getLine
          putStrLn . show $ solve t

solve :: Integer -> Integer
solve 0 = 1
solve 1 = 1
solve n = (2*(solve (n-1)) + (fact n) - (fact (n-1))) `mod` 998244353
    where fact n = foldl (\s -> \t -> s*(snd t + 1)) 1 (Map.toList . factorization  $ n)
    --the number of divisors of a number

--copied from Internet,infinite prime list
primes :: [Integer]
primes = 2: 3: sieve (tail primes) [5,7..]
  where
    sieve (p:ps) xs = h ++ sieve ps [x | x <- t, x `rem` p /= 0]
      where (h,~(_:t)) = span (< p*p) xs

--make factorization of a number
factorization :: Integer -> Map.Map Integer Integer
factorization 1 = Map.fromList []
factorization x = Map.insertWith (+) factor 1 (factorization (x `div` factor))
    where factor = head $ filter (\s -> (x `mod` s) == 0) ls
          ls = primes

该程序未能在规定时间内解决。

那么谁能指出我哪里做错了以及如何解决?
或者在时间限制内使用 Haskell 来解决这个问题是不可能的?

2 个答案:

答案 0 :(得分:6)

时间复杂度不是最优的有很多方法。最明显的是使用试除法而不是例如筛子的素数查找器。也许这很好,因为您只计算了一次质数,但这并不能激发信心。

factorization 也至少有一个明显的问题。考虑对像 78893012641 这样的数进行因式分解,其质数因式分解为 280879^2。您将搜索每个质数直至 280879:昂贵,但几乎不可避免。但是,此时您除以 280879,然后尝试对 280879 进行因式分解,从 2 开始,再次扫描所有小素数,即使您刚刚发现它们都不是因数!

正如 Li-yao Xia 在评论中所说,我也会怀疑非常大的 Integer 在取模之前的乘法,而不是在每次乘法之后取模。

答案 1 :(得分:1)

您没有从“Internet”复制正确的代码段。您应该为素数列表复制 primesTMWE,但更重要的是,为分解算法复制 primeFactors

您基于 foldl 的数字因式分解的除数计算非常好,除非可能应该使用 foldl'

请注意,solve nsolve (n-1) 都计算 fact (n-1),因此最好预先计算所有这些..... 也许存在更好的算法来从1n 而不是分别为每个数字计算。

我怀疑即使使用正确的算法(我在上面链接),如果您要独立分解每个数字(O(n) 数字, O(n1/2)) 时间分解每个...每个素数,至少)。

也许这里要尝试的是最小因子筛网,它可以构建在O(n log log n) 像往常一样使用 Eratosthenes 的筛子,一旦它建立起来,它就可以让您在 O(log log n) 时间内找到每个数字的因式分解(它是数字的 average number of prime factors)。不过,它必须构建为 n(当然,您可以将偶数设为特殊情况以将空间需求减半;或者使用 6-coprimes 来节省另一个 1/6)。可能是 as an STUArray(该链接是一个示例;可以找到更好的代码 here on SO)。

最小因子筛法就像埃拉托色尼筛法一样,只是它使用最小因子作为标记,而不仅仅是布尔值。

为了找到一个数的因式分解,我们只需重复删除一个数的最小因数 n / sf(n) =: n1,重复 n1 / sf(n1) =: n2,然后 n2 等,直到我们找到一个质数(即任何以本身为最小因数的数)。

由于您只使用这些因数来计算数字的总除数,因此您可以将这两个计算融合到一个连接循环中,以提高效率。

相关问题