Полювання на яйця в стилі Collatz


11

Натхненний Великим API на великодні яйце полювання!

Підсумок

Ваше завдання - пошук заздалегідь заданого цілого числа в "просторі Колаца" (пояснюється пізніше), використовуючи найменший можливий крок.

Вступ

Цей виклик ґрунтується на знаменитій гіпотезі Колатца, про яку, сподіваємось, всі тут принаймні чули. Ось резюме, узяте з друку номерів Super Collatz .

Коллатц послідовність (також звана проблемою 3x + 1), де ви починаєте з будь-яким позитивним цілим числом, для цього прикладу ми будемо використовувати 10, і застосувати цей набір кроків до нього:

if n is even:
    Divide it by 2
if n is odd:
    Multiply it by 3 and add 1
repeat until n = 1

Відстань Колатца C(m,n)між цими двома числами mі nдля цієї задачі - це відстань між двома числами в графі Колатца (Подяки до @tsh за те, що вони розповіли мені про цю концепцію), яка визначається наступним чином: (використовуючи 21та 13як приклади ):

Запишіть послідовність Collatz для m(у цьому випадку 21):

21, 64, 32, 16, 8, 4, 2, 1

Запишіть послідовність Collatz для n(у цьому випадку 13):

13, 40, 20, 10, 5, 16, 8, 4, 2, 1

Тепер порахуйте, скільки цифр з’являється лише в одній із послідовностей. Це визначається як відстань Колац між mі n. У цьому випадку 8, а саме,

21, 64, 32, 13, 40, 20, 10, 5

Отже, у нас є відстань Колац між 21і 13як C(21,13)=8.

C(m,n) мають такі приємні властивості:

C(m,n)=C(n,m)
C(m,n)=0 iff. m=n

Сподіваємось, визначення цього поняття C(m,n)зрозуміло. Почнемо займатися полюванням на яйця в просторі Колац!

На початку гри контролер визначає положення пасхального яйця, яке виражається його одновимірною координатою: Ціле число в проміжку [p,q](іншими словами, ціле число між pі qобома кінцями включно).

Положення яйця залишається постійним протягом всієї гри. Ми позначимо цю координату як r.

Тепер ви можете зробити початкову здогадку 0 , і це буде записано контролером. Це ваш 0 раунд. Якщо вам пощастило, що ви потрапили на перше місце (тобто 0 = r), гра закінчується, і ваш рахунок 0(чим менший рахунок, тим краще). В іншому випадку ви входите в 1-й раунд і робите нову здогадку 1 , це триває до тих пір, поки ви не зробите це правильно, тобто n = r, і ваш рахунок буде n.

Протягом кожного раунду після 0-го, контролер надає вам одну з наступних зворотних зв'язків, щоб ви могли краще здогадатися, виходячи з наданої інформації. Припустимо, ви зараз перебуваєте на nдругому раунді, і тому ви здогадаєтесь, що це n

  • "Ви знайшли це!" якщо n = r, то в цьому випадку гра закінчується, і ви забиваєте рахунок n.
  • "Ти ближче :)", якщо C (a n , r) <C (a n-1 , r)
  • "Ви кружляєте навколо яйця", якщо C (a n , r) = C (a n-1 , r)
  • "Ви далі :(" якщо C (a n , r)> C (a n-1 , r)

Щоб зберегти деякі байти, я буду називати відповіді як "Правильно", "Ближче", "Те саме", "Далі", у порядку, представленому вище.

Ось приклад гри з p=1,q=15.

  • a 0 = 10
  • a 1 = 11, відповідь: "Ближче"
  • a 2 = 13, відповідь: "Далі"
  • a 3 = 4, відповідь: "Далі"
  • a 4 = 3, відповідь: "Ближче"
  • a 5 = 5, відповідь: "Те саме"
  • a 6 = 7, відповідь: "Правильно"

Оцінка: 6.

Виклик

Розробіть детерміновану стратегію для гри в гру p=51, q=562з найкращим балом.

У відповідях слід детально описати алгоритми. Ви можете приєднати будь-який код, який допомагає з'ясувати алгоритм. Це не кодогольф, тому вам пропонується написати розбірливий код.

Відповіді повинні включати найгірший бал, який вони можуть досягти за всі можливі випадки r, і виграє той, хто має найнижчий гірший бал. У разі зрівняння rвиграють алгоритми, які мають кращу середню оцінку за всі можливі s (які також повинні бути включені у відповіді). Більше немає перерв, і ми можемо мати кілька переможців у підсумку.

Технічні характеристики

Баунті (додано після публікації першої відповіді)

Я особисто можу запропонувати щедро відповідь, де всі здогадки робляться в межах діапазону, в [51,562]той час як все ще є досить низький найгірший бал.


У вас є контролер?
користувач202729

Не той, який схожий на той, що в оригінальному питанні.
Вейджун Чжоу

1
C (m, n) - відстань m, n на графіку Колатца .
tsh

Я сам придумав цю концепцію і не знав графіка Колатца. Дякую, що ти мені це сказав. Я включу інформацію у запитання.
Вейджун Чжоу

Відповіді:


5

Рубі, 196

Це було набагато важче, що я спочатку думав. Мені довелося обробляти багато незрозумілих справ і в кінцевому підсумку було багато некрасивого коду. Але це було дуже весело! :)

Стратегія

Кожна послідовність Колатца закінчується послідовністю потужностей 2 (наприклад: [16, 8, 4, 2, 1]). Як тільки виникає потужність 2, ми ділимо на 2, поки не досягнемо 1. Назвемо першу потужність 2 у послідовності, найближчій pow2 (оскільки це також найближча потужність 2 до нашого числа, використовуючи відстань Колатца). Для даного діапазону (51-562) усі можливі найближчі числа pow2 : [16, 64, 128, 256, 512, 1024]

Коротка версія

Алгоритм виконує:

  • двійковий пошук, щоб визначити найближчу потужність 2 до поточного числа
  • лінійний пошук для з'ясування кожного попереднього елемента в послідовності, поки не буде виявлено цільове число.

Детальна версія

Враховуючи гру з цільовим номером r, стратегія така:

  1. Використовуйте двійковий пошук, щоб визначити потужність 2, яка є найближчою, rза якомога менше кроків.
  2. Якщо найближча потужність 2, яка була знайдена, - це рішення, зупиніть. В іншому випадку продовжуйте 3.
  3. Оскільки потужність 2, яку було знайдено, - це перша потужність 2, що виникає в послідовності, якщо випливає, що це значення було досягнуто виконанням (* 3 + 1) операції. (Якби це було після операції / 2, то попереднє число також було б потужністю 2). Обчислити попереднє число в послідовності, виконавши операцію зворотного зв'язку (-1, а потім / 3)
  4. Якщо це число - ціль, зупиніться. В іншому випадку продовжуйте 5.
  5. З огляду на поточне число, відоме з послідовності, потрібно повернутися назад і виявити попереднє число в послідовності. Невідомо, чи отримано поточне число операцією (/ 2) або (* 3 +1), тому алгоритм намагається їх обидва і бачить, яке з них отримує число, яке ближче (як відстань Колатца) від цілі .
  6. Якщо нововиявлене число є правильним, зупиніться.
  7. Використовуючи щойно відкритий номер, поверніться до кроку 5.

Результати

Запуск алгоритму для всіх чисел в діапазоні 51-562 займає приблизно секунду на звичайному ПК, а загальна оцінка - 38665.

Код

Спробуйте в Інтернеті!

require 'set'

# Utility methods
def collatz(n)
  [].tap do |results|
    crt = n
    while true
      results << crt
      break if crt == 1
      crt = crt.even? ? crt / 2 : crt * 3 + 1
    end
  end
end

def collatz_dist(m, n)
  cm = collatz(m).reverse
  cn = collatz(n).reverse
  common_length = cm.zip(cn).count{ |x, y| x == y }
  cm.size + cn.size - common_length * 2
end



GuessResult = Struct.new :response, :score
# Class that can "play" a game, responding
# :right, :closer, :farther or :same when
# presented with a guess
class Game

  def initialize(target_value)
    @r = target_value
    @score = -1
    @dist = nil
    @won = false
  end
  attr_reader :score

  def guess(n)
    # just a logging decorator over the real method
    result = internal_guess(n)
    p [n, result] if LOGGING
    result
  end

  private

  def internal_guess(n)
    raise 'Game already won!' if @won
    @score += 1
    dist = collatz_dist(n, @r)
    if n == @r
      @won = true
      return GuessResult.new(:right, @score)
    end
    response = nil
    if @dist
      response = [:same, :closer, :farther][@dist <=> dist]
    end
    @dist = dist
    GuessResult.new(response)
  end

end

# Main solver code

def solve(game)
  pow2, won = find_closest_power_of_2(game)
  puts "Closest pow2: #{pow2}" if LOGGING

  return pow2 if won
  # Since this is the first power of 2 in the series, it follows that
  # this element must have been arrived at by doing *3+1...
  prev = (pow2 - 1) / 3
  guess = game.guess(prev)
  return prev if guess.response == :right

  solve_backward(game, prev, 300)
end

def solve_backward(game, n, left)
  return 0 if left == 0
  puts "***      Arrived at  ** #{n} **" if LOGGING
  # try to see whether this point was reached by dividing by 2
  double = n * 2
  guess = game.guess(double)
  return double if guess.response == :right

  if guess.response == :farther && (n - 1) % 3 == 0
    # try to see whether this point was reached by *3+1
    third = (n-1) / 3
    guess = game.guess(third)
    return third if guess.response == :right
    if guess.response == :closer
      return solve_backward(game, third, left-1)
    else
      game.guess(n) # reset to n...
    end
  end
  return solve_backward(game, double, left-1)
end


# Every Collatz Sequence ends with a sequence of powers of 2.
# Let's call the first occurring power of 2 in such a sequence
# POW2
#
# Let's iterate through the whole range and find the POW2_CANDIDATES
#
RANGE = [*51..562]
POWERS = Set.new([*0..15].map{ |n| 2 ** n })

POW2_CANDIDATES =
  RANGE.map{ |n| collatz(n).find{ |x| POWERS.include? x} }.uniq.sort
# Turns out that the candidates are [16, 64, 128, 256, 512, 1024]

def find_closest_power_of_2(game)
  min = old_guess = 0
  max = new_guess = POW2_CANDIDATES.size - 1
  guess = game.guess(POW2_CANDIDATES[old_guess])
  return POW2_CANDIDATES[old_guess], true if guess.response == :right
  guess = game.guess(POW2_CANDIDATES[new_guess])
  return POW2_CANDIDATES[new_guess], true if guess.response == :right
  pow2 = nil

  while pow2.nil?

    avg = (old_guess + new_guess) / 2.0

    case guess.response
    when :same
      # at equal distance from the two ends
      pow2 = POW2_CANDIDATES[avg.floor]
      # still need to test if this is correct
      guess = game.guess(pow2)
      return pow2, guess.response == :right
    when :closer
      if old_guess < new_guess
        min = avg.ceil
      else
        max = avg.floor
      end
    when :farther
      if old_guess < new_guess
        max = avg.floor
      else
        min = avg.ceil
      end
    end

    old_guess = new_guess
    new_guess = (min + max) / 2
    new_guess = new_guess + 1 if new_guess == old_guess
    # so we get next result relative to the closer one
    # game.guess(POW2_CANDIDATES[old_guess]) if guess.response == :farther
    guess = game.guess(POW2_CANDIDATES[new_guess])

    if guess.response == :right
      pow2 = POW2_CANDIDATES[new_guess]
      break
    end

    if min == max
      pow2 = POW2_CANDIDATES[min]
      break
    end

  end

  [pow2, guess.response == :right]

end



LOGGING = false

total_score = 0
51.upto(562) do |n|
  game = Game.new(n)
  result = solve(game)
  raise "Incorrect result for #{n} !!!" unless result == n
  total_score += game.score
end
puts "Total score: #{total_score}"

Вражає. Є незначний момент: я вважаю, що один із коментарів не повинен говорити "ідеальний квадрат".
Вейджун Чжоу

1
@WeijunZhou Ви праві. Виправлено!
Крістіан Лупаску

Вам, мабуть, слід включити найгірший бал за всі випадки, який становить 196.
Вейджун Чжоу

3

найгірший бал: 11, підсумок: 3986

Усі здогадки знаходяться в діапазоні [51,562].

Мій алгоритм:

  1. Перший час вгадайте 512 та підтримуйте набір можливих результатів vals, спочатку набір містить усі числа в діапазоні [51,562].
  2. На кожному кроці виконайте наступне:

    1. Знайти значення наступного припущення guessв діапазоні [51,562]таким чином , що , коли значення в vals( за винятком guessсамого себе) розбивається на 3 наборів відповідних можливих результатів Closer, Sameі Farther, розмір максимум з цих 3 -х наборів мінімальна.
      Якщо існує декілька можливих значень guessзадоволення зазначеного, виберіть найменше.
    2. Відгадайте значення guess.
    3. Якщо відповідь «Правильно», зробіть (вийдіть з програми).
    4. Видаліть усі значення з набору valsтаким чином, що вони не зможуть дати результат.

Моя контрольна реалізація, написана на C ++, і Bash працює на моїй машині приблизно за 7,6 секунд і дає найгірший бал / суму, як описано в заголовку.

Якщо спробувати всі можливі значення першої здогадки, на моїй машині знадобиться приблизно 1,5 години. Я можу розглянути це.



Але якщо ви дійсно хочете, щоб він працював, не повторюючи його з якихось причин, спробуйте в Інтернеті !
користувач202729

Почекайте хвилинку, чому я не можу просто дозволити моїй програмі вивести дерево рішень і оцінити його: | було б набагато швидше ...
user202729
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.