Подсчет массивов, которые действительно уникальны

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 уникальным, если нет другого массива Bтакой же длины f(A) = f(B), кроме массива в Aобратном порядке. Как пример, f((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для которого это завершается за одну минуту.

тестирование

Мне нужно будет запустить ваш код на моей машине с Ubuntu, поэтому, пожалуйста, включите как можно более подробные инструкции по компиляции и запуску вашего кода.

Leaderboard

  • Кристиан Сиверс из Хаскелла, n = 13 (42 секунды)
Ануш
источник
Сколько памяти нам разрешено потреблять?
Черная Сова Кай
@ BlackOwlKai Моя машина имеет 8 ГБ, так что я думаю, 6 ГБ безопасно?
Anush
Я думаю, что последнее число в примерах должно быть 472 вместо 427.
Кристиан Сиверс
@ChristianSievers Спасибо. Исправлено сейчас.
Ануш
Что такое s? Что это представляет?
Gigaflop

Ответы:

5

Haskell

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следует по сути той же логике, что и эта , но имеет дополнительный аргумент, дающий сумму, которую должны иметь оставшиеся записи списка. Это позволяет рано останавливаться, когда даже самые маленькие возможные значения слишком велики, чтобы получить эту сумму, что дало значительное улучшение производительности. Дальнейшие большие улучшения были получены путем перемещения этого теста на более раннее место (версия 2) и замены списка текущих сумм на Vector(версия 3 (не работает) и 4 (с дополнительной строгостью)). В последней версии тест на членство в наборе устанавливается с помощью таблицы поиска и добавляет еще больше строгости и распараллеливания.

Когда constructнайден список, в котором указаны суммы подсписков и он меньше его обратного, он может его вернуть, но мы на самом деле не заинтересованы в нем. Было бы почти достаточно просто вернуться, ()чтобы указать на его существование, но нам нужно знать, нужно ли нам считать его дважды (потому что это не палиндром, и мы никогда не справимся с его обратным). Таким образом, мы помещаем 1 или 2 weightв список результатов.

Функция countобъединяет эти части. Для каждого набора сумм подсписков (поступающих из orig), который является уникальным среди списков, содержащих только sи s+1, он вызывает value, который вызывает constructи, посредством uniqueval, проверяет, есть ли только один результат. Если это так, то это вес, который мы должны посчитать, иначе набор сумм не будет уникальным и возвращается ноль. Обратите внимание, что из-за лени, constructостановится, когда найдет два результата.

В mainфункции ручки ввода - вывода и петля sот 1 до 9.

Компиляция и запуск

На Debian нужны пакеты ghc, libghc-vector-devи libghc-parallel-dev. Сохраните программу в файл prog.hsи скомпилируйте его ghc -threaded -feager-blackholing -O2 -o prog prog.hs. Запустите с ./prog <n> +RTS -Nгде <n>длина массива, для которого мы хотим подсчитать уникальные массивы.

Кристиан Сиверс
источник
Этот код довольно удивительный (и короткий!). Если бы вы могли добавить какое-то объяснение, я уверен, что люди хотели бы понять, что вы сделали.
Ануш
Ваша новая версия не компилируется для меня. Я получаю bpaste.net/show/c96c4cbdc02e
Anush
Извините, удаление и вставка большего кода настолько неудобны, что иногда я просто меняю несколько строк вручную. Конечно, я допустил ошибку ... Исправлено сейчас (надеюсь) и добавлено еще одно улучшение, на этот раз только на несколько процентов. Другие изменения были гораздо важнее.
Кристиан Сиверс