本当にユニークな配列を数える


9

これは、一意のセットを作成するCount配列のフォローアップです。大きな違いは、一意性の定義です。

A長さの配列を考えnます。配列には正の整数のみが含まれます。例えばA = (1,1,2,2)f(A)のすべての空でない隣接サブアレイの合計のセットとして定義してみましょうA。この場合f(A) = {1,2,3,4,5,6}。作成する手順f(A) は次のとおりです。

のサブ配列はAです(1), (1), (2), (2), (1,1), (1,2), (2,2), (1,1,2), (1,2,2), (1,1,2,2)。それぞれの合計は1,1,2,2,2,3,4,4,5,6です。したがって、このリストから取得するセットはです{1,2,3,4,5,6}

配列が逆になることを除いて、と同じ長さの配列が他にない場合は、配列をA 一意と呼びます。例として、しかし、同じ合計のセットを生成する長さの他の配列はありません。Bf(A) = f(B)Af((1,2,3)) = f((3,2,1)) = {1,2,3,5,6}3

仕事

所与のためのタスク、nおよびsその長さのユニークな配列の数をカウントすることです。sとの間である1と想定でき9ます。要素が特定の整数sまたはのいずれかである配列をカウントするだけで済みますs+1。たとえば、s=1カウントする配列に1およびのみが含まれている場合2。ただし、一意性の定義は、同じ長さの他の配列に関するものです。具体的な例としては、と同じ合計のセットを提供するため、一意で[1, 2, 2, 2]はありません[1, 1, 2, 3]

配列の逆数と配列自体を数える必要があります(配列がもちろんパリンドロームでない限り)。

s = 1、n = 2、3、4、5、6、7、8、9の回答は次のとおりです。

4, 3, 3, 4, 4, 5, 5, 6

の場合s = 1、長さ4の一意の配列は

(1, 1, 1, 1)
(2, 1, 1, 2)
(2, 2, 2, 2)

s = 2、n = 2、3、4、5、6、7、8、9の回答は次のとおりです。

4, 8, 16, 32, 46, 69, 121, 177

と一意でs = 2はない配列の例は次のとおりです。

(3, 2, 2, 3, 3, 3). 

これには、との両方と同じ合計のセットが(3, 2, 2, 2, 4, 3)あり(3, 2, 2, 4, 2, 3)ます。

s = 8、n = 2、3、4、5、6、7、8、9の回答は次のとおりです。

4, 8, 16, 32, 64, 120, 244, 472

スコア

与えられたためにn、あなたのコードが出力のすべての値に対して答える必要がありますsから1にします9。あなたのスコアは、nこれが1分で完了する最高値です。

テスト中

私のubuntuマシンでコードを実行する必要があるので、コードをコンパイルして実行する方法について可能な限り詳細な手順を含めてください。

リーダーボード

  • n = 13Haskellの Christian Sievers (42秒)

どのくらいのメモリを消費できますか?
ブラックフクロウカイ

@BlackOwlKai私のマシンには8GBがあるので、6GBは安全だと思いますか?
Anush

私は、例の最後の数は472ではなく427であるべきだと思う
クリスチャンSieversの

@ChristianSieversありがとうございます。今修正されました。
アヌッシュ、

なにs?それは何を表していますか?
ギガフロップ2018年

回答:


5

ハスケル

import Control.Monad (replicateM)
import Data.List (tails)
import qualified Data.IntSet as S
import qualified Data.Map.Strict as M
import qualified Data.Vector.Unboxed as V
import Data.Vector.Unboxed.Mutable (write)
import System.Environment (getArgs)
import Control.Parallel.Strategies

orig:: Int -> Int -> M.Map S.IntSet (Maybe Int)
orig n s = M.fromListWith (\ _ _ -> Nothing) 
               [(sums l, Just $! head l) | 
                   l <- replicateM n [s, s+1],
                   l <= reverse l ]

sums :: [Int] -> S.IntSet
sums l = S.fromList [ hi-lo | (lo:r) <- tails $ scanl (+) 0 l, hi <- r ]

construct :: Int -> Int -> S.IntSet -> [Int]
construct n start set =
   setmax `seq` setmin `seq` setv `seq`
   [ weight r | r <- map (start:) $ constr (del start setlist)
                                           (V.singleton start)
                                           (n-1)
                                           (setmax - start),
                r <= reverse r ]
  where
    setlist = S.toList set
    setmin = S.findMin set
    setmax = S.findMax set
    setv = V.modify (\v -> mapM_ (\p -> write v p True) setlist)
                    (V.replicate (1+setmax) False)

    constr :: [Int] -> V.Vector Int -> Int -> Int -> [[Int]]
    constr m _ 0 _ | null m    = [[]]
                   | otherwise = []
    constr m a i x =
         [ v:r | v <- takeWhile (x-(i-1)*setmin >=) setlist,
                 V.all (V.unsafeIndex setv . (v+)) a,
                 let new = V.cons v $ V.map (v+) a,
                 r <- (constr (m \\\ new) $! new) (i-1) $! (x-v) ]

del x [] = []
del x yl@(y:ys) = if x==y then ys else if y<x then y : del x ys else yl

(\\\) = V.foldl (flip del)

weight l = if l==reverse l then 1 else 2

count n s = sum ( map value [ x | x@(_, Just _) <- M.toList $ orig n s]
                      `using` parBuffer 128 rseq )
  where 
    value (sms, Just st) = uniqueval $ construct n st sms
    uniqueval [w] = w
    uniqueval _   = 0


main = do
  [ n ] <- getArgs
  mapM_ print ( map (count (read n)) [1..9]
                    `using` parBuffer 2 r0 )

このorig関数はn、エントリsまたはで長さのすべてのリストを作成し、s+1それらが逆の前にある場合はそれらを保持し、サブリストsumsを計算して、リストの最初の要素も記憶するマップにそれらを配置します。同じ合計のセットが複数見つかった場合、最初の要素はに置き換えられるNothingため、これらの合計を取得するために他の方法を探す必要がないことがわかります。

このconstruct関数は、指定された一連のサブリスト合計を持つ、指定された長さと開始値のリストを検索します。その再帰部分constrは、基本的にthisと同じロジックに従いますが、残りのリストエントリに必要な合計を与える追加の引数があります。これにより、可能な最小の値でもこの合計を得るには大きすぎる場合、早期に停止することができ、大幅なパフォーマンスの向上をもたらしました。このテストを以前の場所(バージョン2)に移動し、現在の合計のリストをVector(バージョン3(破損)および4(厳密性を追加))に置き換えることにより、さらに大きな改善が得られました。最新バージョンは、ルックアップテーブルを使用してセットメンバーシップテストを実行し、さらに厳密さと並列化を追加します。

constructがサブリストの合計を与え、その逆よりも小さいリストを見つけた場合、それはそれを返す可能性がありますが、実際には興味がありません。単に()その存在を示すために戻るだけでほぼ十分ですが、2回カウントする必要があるかどうかを知る必要があります(回文ではないため、その逆を処理することはないため)。したがってweight、結果のリストに1または2を入れます。

関数countは、これらの部分を組み合わせます。サブリストの合計のセットごとに(からのorig)のみを含むリストの中でユニークであったことss+1、それを呼び出しvalue、その呼び出しconstructを介して、そしてuniqueval唯一の結果が存在するかどうかを確認します。もしそうなら、それは私たちが数えなければならない重みです、そうでなければ、合計のセットは一意ではなく、ゼロが返されます。怠惰のため、construct2つの結果が見つかったときに停止します。

このmain関数は、IOとs1〜9のループを処理します。

コンパイルと実行

Debianでは、このパッケージを必要とするghclibghc-vector-devlibghc-parallel-dev。プログラムをファイルに保存し、prog.hsでコンパイルしghc -threaded -feager-blackholing -O2 -o prog prog.hsます。で実行./prog <n> +RTS -Nところ<n>、我々はユニークな配列をカウントしたい配列の長さです。


このコードはかなり素晴らしい(そして短い!)。もしあなたが何らかの説明を付け加えることができれば、私は人々があなたがしたことを理解したいと思うでしょう。
アヌッシュ、

新しいバージョンではコンパイルできません。私が手bpaste.net/show/c96c4cbdc02e
Anush

申し訳ありませんが、大きなコードの削除と貼り付けは非常に不快なので、数行だけを手動で変更することもあります。もちろん、私は間違いを犯しました...今は修正済み(私はそう思います)で、今度は数パーセントだけ別の改善を加えました。他の変更ははるかに重要でした。
クリスチャンシーバーズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.