在Haskell中解决Google Code Jam的“最小标量产品”

时间:2012-03-16 18:35:20

标签: haskell

为了准备即将推出的Google Code Jam,我已经开始研究一些问题了。这是我尝试过的一个练习问题:

http://code.google.com/codejam/contest/32016/dashboard#s=p0

这是我当前的Haskell解决方案的要点:

{-
- Problem URL: http://code.google.com/codejam/contest/32016/dashboard#s=p0
-
- solve takes as input a list of strings for a particular case
- and returns a string representation of its solution.
-}

import Data.List    

solve :: [String] -> String
solve input = show $ minimum options
    where (l1:l2:l3:_) = input
          n = read l1 :: Int
          v1 = parseVector l2 n
          v2 = parseVector l3 n
          pairs = [(a,b) | a <- permutations v1, b <- permutations v2]
          options = map scalar pairs

parseVector :: String -> Int -> [Int]
parseVector line n = map read $ take n $ (words line) :: [Int]

scalar :: ([Int],[Int]) -> Int
scalar (v1,v2) = sum $ zipWith (*) v1 v2

这适用于提供的示例输入,但在小输入上死于内存不足错误。增加最大堆大小似乎什么都不做,只是让它无限期地运行。

我的主要问题是如何优化它,以便它实际上会返回一个解决方案,而不是将-O标志传递给ghc,我已经完成了。

4 个答案:

答案 0 :(得分:7)

改善算法是最重要的。目前,您可以对两个列表进行置换,并提供总共(n!)^2个选项进行检查。您只需要列出其中一个列表。这不仅应该减少时间复杂度,还应该大大减少空间使用。 并确保将minimum替换为严格的最小值(因为它应该优化,因为您正在使用Int列表的最小值,使用-ddump-rule-firings进行编译并检查为“minimumInt”)。如果不是,请使用foldl1' min而不是最低。

我刚检查过:

Large dataset

T = 10
100 ≤ n ≤ 800
-100000 ≤ xi, yi ≤ 100000

为此,您需要一个更好的算法。 100!约为9.3 * 10 157 ,在检查100个元素的所有排列的可测量部分之前,宇宙将长期存在。您必须通过查看数据来构建求解排列。为了找出解决方案的样子,研究一些小的输入以及哪些排列实现了那些最小的标量乘积(看看Cauchy-Bun'akovskiy-Schwarz不等式也不会受到伤害)。

答案 1 :(得分:4)

我提出的解决方案:

{-
- Problem URL: http://code.google.com/codejam/contest/32016/dashboard#s=p0
-
- solve takes as input a list of strings for a particular case
- and returns a string representation of its solution.
-}

import Data.List  

solve :: [String] -> String
solve input = show result
    where (l1:l2:l3:_) = input
          n = read l1 :: Int
          v1 = parseVector l2 n
          v2 = parseVector l3 n
          result = scalar (sort v1) (reverse $ sort v2)

parseVector :: String -> Int -> [Integer]
parseVector line n = map read $ take n $ (words line) :: [Integer]

scalar :: [Integer] -> [Integer] -> Integer
scalar v1 v2 = sum $ zipWith (*) v1 v2 :: Integer

这似乎给出了正确答案,并且速度提高了几个数量级。我猜的诀窍是在阅读这样的问题时,不要在表面上采用“排列”这个词。

答案 2 :(得分:1)

根据我(我的解决方案工作),你真的不需要置换, 你真的需要对两个列表进行排序,然后你需要队列和堆栈,
它是如何工作的 例如: -
输入
1 3 -5
-2 1 4

排序
-5 1 3
-2 1 4

然后-2 * 3 + 4 * -5 + 1 * 1 = -6

EX2: -
所以你看到了 -ve数乘以最+ ve或最小-ve
+ ve数乘以最多-ve或最少+ ve

答案 3 :(得分:0)

  

我的主要问题是如何优化它以便它实际上会返回一个解决方案,而不是将-O标志传递给ghc,而我已经完成了。

我认为你应该重新考虑你的方法。我认为这个练习题不是编码技巧,而是解决问题。尝试所有可能的排列是蛮力的方式,我认为你现在有一种良好的感觉为什么这种暴力策略在这种情况下不起作用;)。当你使用stl算法时(当然不包括输入和输出),它可以在3行代码中完成。

实际上Madan Ram已经提供了某种解决方案。但是,如果您在面试中遇到这样的问题,您还必须知道它为什么适用于所有情况(不仅仅是一些例子)。所以我的建议是拿一支笔和一张纸,并手工做一些例子,然后你会发现如何做到这一点。

SPOILER ALERT

很难给出一个暗示而不会破坏太多,但是因为已经发布了解决方案...尝试开始简单。第一个向量中哪个元素对标量积有最大贡献?你需要将第二个向量中的哪个元素多为多个,以获得对标量乘积的最小贡献?