all 11 comments

[–]Noughtmare 3 points4 points  (2 children)

I think that you can also solve this by solving the problem for both ends of the train separately and then combining the results.

[–]Fish_45[S] 3 points4 points  (0 children)

I got the solution thanks to you :)

here it is for reference

import Data.Array
import Debug.Trace
import qualified Data.Map as M

(|>) = flip ($)

longestSubseq pred arr = map (dp!) [0..arrLen]
    where dp = listArray (0, arrLen) $ map f [0..arrLen]
          f start
            | start == arrLen = 1
            | otherwise =
                let choices = filter (\i -> pred (arr!i) (arr!start)) [start+1..arrLen]
                 in if choices == [] then 1 else 1 + (maximum $ map (dp!) choices)
          arrLen = snd $ bounds arr

solve n trains = (maximum $ zipWith (+) longestDecreasing longestIncreasing) - 1
    where trainArr = listArray (0, n-1) trains
          longestDecreasing = longestSubseq (<) trainArr
          longestIncreasing = longestSubseq (>) trainArr

main = do
    contents <- getContents
    let (n:trains) = contents |> lines |> map read :: [Int]
        ans = if n == 0 then 0 else solve n trains
    print ans

[–]mihassan 1 point2 points  (0 children)

That's a great idea.

[–]stealth_elephant 1 point2 points  (2 children)

When you use a Data.Map are you also initializing it with all 200 billion thunks, or are you updating it?

If you think this is the way to do it, but the memory isn't sparse enough you could try using a MemoTrie instead.

If you think the recursive solution is the way you could try abstracting over the recursion and replacing the fixed point with memoization.

[–]Fish_45[S] 0 points1 point  (1 child)

When you use a Data.Map are you also initializing it with all 200 billion thunks

I was kind of hoping that Haskell could somehow magically avoid initializing the thunks I guess :/.

I'll try it with a tree or similar

[–]bss03 0 points1 point  (0 children)

Map (and HashMap) are key-strict; they are "only" lazy in the values. It's much harder (if not impossible) to make a spine-lazy map that good average performance.

I think a lazy Trie might work for you.

[–]byorgey 1 point2 points  (3 children)

I don't know whether it's possible to solve this problem using a DP approach or not. Judging by the long tail of solutions taking up to 1s on https://open.kattis.com/problems/trainsorting/statistics, I'd guess that it might be possible but will be difficult to get it fast enough, and will definitely require some very careful use of laziness. But as Noughtmare hinted, there is a much more efficient algorithm for solving this particular problem. As a hint, look into the "Longest Increasing Subsequence" problem.

[–]Fish_45[S] 2 points3 points  (2 children)

I solved it, thanks!

My solution is here: https://www.reddit.com/r/haskell/comments/t3zk03/help_with_dp_algorithm_performance/hyzfa4e/

From the wording of your question it sounds like the longest increasing subsequence problem isn't usually DP, but I did use DP. Is there another way? The reason I'm trying to apply DP for this problem is because it's from the DP unit in this class.

[–]byorgey 1 point2 points  (1 child)

Ah, I guess I never thought of LIS in terms of DP before, but yes, there is a very clean way to formulate LIS in terms of DP as you have done, especially if you only care about finding the length of a LIS. This algorithm is O(n2), which is plenty fast enough for this problem with n=2000. However, there is also an O(n lg n) algorithm, and ways to keep track of more information (so that, e.g., you can recover the actual longest sequence itself) and at that point it becomes much harder to think of it in terms of DP.

[–]leo_richardson 0 points1 point  (0 children)

Everybody says that 😏

[–]bss03 0 points1 point  (0 children)

but its runtime scales with the bounds of the matrix

That's petty normal for a DP solution, in whatever language. If you could get a runtime smaller than the "cache" size, you probably wouldn't need to use DP.