В рамках серии экспериментов с прокачкой мозгов , я решил принять участие в небольшом программистском конкурсе от Романа Душкина . И я не без удовольствия воспринял новость о том , что вошел в пятерку победителей, полностью решивших задачу (места не пронумерованы, но если бы были, то я бы точно занял не первое и не второе место).

Конкурс состоял в том, чтобы написать программу (на любом языке), решающую следующую задачу:

Трое крестьян — Иван, Петр и Алексей — пришли на рынок с женами — Марией, Екатериной и Анной. Каждый из этих шести человек заплатил за каждый купленный предмет столько копеек, сколько предметов он купил.

Каждый мужчина истратил на 48 копеек больше своей жены.

Кроме того, Иван купил на 9 предметов больше Екатерины, а Петр — на 7 предметов больше Марии.

Кто на ком женат?

Также после решения задачи требовалось ответить на кое-какие дополнительные вопросы, которые заранее были неизвестны.

В первую очередь я решил составить математическую модель задачи. Пусть m i — количество предметов, купленных i-м мужчиной, а w j — количество предметов, купленных j-ой женщиной, где i, j ∈ {0,1,2} . Номер женщины, на которой женат i-ый мужчина, обозначим x i . Дано:

m i 2 — w x i 2 = 48
m 0 — w 1 = 9
m 1 — w 0 = 7
m i , w j ∈ ℕ 0
i, j ∈ {0,1,2}

Требуется найти такое множество {x i } , чтобы приведенная система уравнений имела решение для каких-нибудь {m i } и {w j } .

Теперь задачу несложно решить простым перебором, для реализации которого был выбран Haskell :

— Решение задачи «Кто на ком женат»
— (c) Alexandr A Alexeev 2011 | http://remontka.com/
import Data . List
— для простоты реализации перебираем число покупок «тупым» способом
maxNumbers = 20
itemsNumbersM =
[ [ x , y , z ] |
x <- [ 1 .. maxNumbers ] , y <- [ 1 .. maxNumbers ] , z <- [ 1 .. maxNumbers ] ]
itemsNumbersW =
[ [ x , y , z ] |
x <- [ 1 .. maxNumbers ] , y <- [ 1 .. maxNumbers ] , z <- [ 1 .. maxNumbers ] ]

— заплачено денег в зависимости от числа покупок
paid x = x * x

solve =
[ ( x , m , w ) |
— i-ый мужчина женат на Xi-ой женщине
x <- permutations [ 0 .. 2 ] ,
— все возможные варианты, сколько купилa i-ая женщина
w <- itemsNumbersW ,
— все возможные варианты, сколько купил i-ый мужчина
m <- itemsNumbersM ,
— кроме того:
( m !! 0 ) ( w !! 1 ) == 9 ,
( m !! 1 ) ( w !! 0 ) == 7 ,
paid ( m !! 0 ) paid ( w !! ( x !! 0 ) ) == 48 ,
paid ( m !! 1 ) paid ( w !! ( x !! 1 ) ) == 48 ,
paid ( m !! 2 ) paid ( w !! ( x !! 2 ) ) == 48
]

main = do
printPair 0
printPair 1
printPair 2
where
man = [ «Ivan» , «Petr» , «Alexey» ]
woman = [ «Maria» , «Katya» , «Anna» ]
( solution , _, _ ) = head solve
printPair n = do
putStrLn $ ( man !! n ) ++ » — » ++ ( woman !! ( solution !! n ) )

Ответ: Иван (13) женат на Анне (11), Петр (8) на Екатерине (4), а Алексей (7) — на Марии (1). В скобках указанно количество купленных вещей.

С первой частью задания справится было легко. Последующие за этим дополнительные вопросы оказались куда сложнее:

1. Сколько уникальных пар коэффициентов (a, b) даёт уникальное распределение людей по парам для разницы в паре в 48 копеек?

2. Для каких значений коэффициента k, меньших или равных 100, задача имеет решения для каких-либо пар коэффициентов (a, b)?

Коэффициенты (a, b) — это разницы типа (9, 7) в условии. Коэффициент k — это как 48 копеек.

Более того, вопросы эти по ряду причин не совсем корректны. Ответить на них с помощью приведенной ранее программы можно, но займет это очень много времени. В общем, после уточнения вопросов и существенной оптимизации изначальной программы, я получил следующее:

— Решение задачи «Кто на ком женат» (с доп. вопросами)
— (c) Alexandr A Alexeev 2011 | http://remontka.com/
import Data . List

— максимальное число покупок у одного человека
maxItemsNum = 100

— список возможных чисел покупок у женщин {W}
— для любого W должен существовать M = sqrt(k + W**2)
possibleItemsNumbersW k =
filter ( x -> manItemsNum k x > 0 ) [ 0 .. maxItemsNum ]

— все возможные пары числа покупок (M, W) : M**2 — W**2 = k
allPossibleItemsNumberMW k =
filter ( ( m , w ) -> m * m w * w == k ) [
( manItemsNum k w1 , w2 ) |
w1 <- possibleItemsNumbersW k ,
w2 <- possibleItemsNumbersW k ]

— все возможные значения a и b для данного k
allPossibleAB k = nub $ filter ( > 0 ) [
manItemsNum k w1 w2 |
w1 <- possibleItemsNumbersW k ,
w2 <- possibleItemsNumbersW k ]

— число покупок мужчины в зависимости от k и числа покупок его жены
manItemsNum k womanItemsNum =
if ( not $ null lst ) && ( ( snd $ lastLst ) == itemsNumberSquare )
then fst $ lastLst
else 1
where
— квадрат искомого значения
itemsNumberSquare = k + womanItemsNum * womanItemsNum
— все квадраты
squares = [ ( x , x * x ) | x <- [ 1 .. ] ]
lst = takeWhile ( ( _, x ) -> x <= itemsNumberSquare ) squares
lastLst = last lst

— все возможные решения задачи для заданных a, b и k
solve a b k =
filter ( ( x , [ w0 , w1 ,_ ] ) ->
( fst ( x !! 0 ) snd ( x !! w1 ) == a ) &&
fst ( x !! 1 ) snd ( x !! w0 ) == b )
[ ( [ pair1 , pair2 , pair3 ] , womanPosList ) |
pair1 <- allPossibleItemsNumberMW k ,
pair2 <- allPossibleItemsNumberMW k ,
pair3 <- allPossibleItemsNumberMW k ,
womanPosList <- permutations [ 0 .. 2 ]
]

— существует ли пара (a,b), дающая единственное решиние для данного k?
hasAnySolutionForK k =
not . null $ filter ( x -> length x == 1 ) [
— если 2 или больше ответа, то для такого k задача некорректна
take 2 $ solve a b k |
a <- allPossibleAB k , b <- allPossibleAB k
]

— Сколько уникальных пар коэффициентов (a, b) даёт уникальное
— распределение людей по парам для разницы в паре в 48 копеек?
task1 =
length $ nub $ filter ( x -> length x == 1 ) [
take 2 $ solve a b k |
a <- allPossibleAB k , b <- allPossibleAB k
]
where k = 48

— Для каких значений коэффициента k, меньших или равных 100,
— задача имеет решения для каких-либо пар коэффициентов (a, b)?
task2 = [ k | k <- [ 1 .. 100 ] , hasAnySolutionForK k ]

main = do
printPair 0
printPair 1
printPair 2
putStrLn $ «task1: » ++ show task1
putStrLn $ «task2: » ++ show task2
where
man = [ «Ivan» , «Petr» , «Alexey» ]
woman = [ «Maria» , «Katya» , «Anna» ]
( items , wifeList ) = head $ solve 9 7 48
printPair n = do
putStrLn $
( man !! n ) ++ » — »
++ ( woman !! ( wifeList !! n ) )
++ » » ++ show ( items !! n )

Ответ на первый вопрос — 12. На второй — 45, 48, 63, 64, 72, 75, 80, 81, 96, 99. Больше всего в ходе написания программы мой уровень ЧСВ подняло осознание того, как же круто я оптимизировал код. Если изначальный вариант программы не мог справиться с задачей за целую ночь, то теперь ответ находится где-то за минуту.

В целом от конкурса я получил исключительно положительные эмоции. А еще я наконец-то понял фразу «главное — участие». Потому что свой главный приз я получил не на финише, а именно во время участия.

Дополнение: А вот и обещанный приз:

Журнал "Потенциал" за август 2009

Если кому-то интересно, это журнал «Потенциал» за август 2009-го года со статьей Романа «Функциональный подход в программировании» и автографом на ней. Приятно, что уж тут 🙂

EnglishRussianUkrainian