Săn trứng theo kiểu Collatz


11

Lấy cảm hứng từ The Great API Easter Egg Hunt!

Tóm lược

Nhiệm vụ của bạn là tìm kiếm một số nguyên được xác định trước trong "không gian Collatz" (sẽ được giải thích sau) bằng cách sử dụng bước ít nhất có thể.

Giới thiệu

Thử thách này dựa trên phỏng đoán Collatz nổi tiếng mà hy vọng mọi người ở đây ít nhất nghe nói đến. Đây là một bản tóm tắt được lấy từ In các số Super Collatz .

Các Collatz trình tự (còn gọi là bài toán 3x + 1) là nơi mà bạn bắt đầu với bất kỳ số nguyên dương, ví dụ này, chúng ta sẽ sử dụng 10, và áp dụng này tập hợp các bước để nó:

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

Khoảng cách Collatz C(m,n)giữa hai số mn, cho mục đích của thử thách này, là khoảng cách giữa hai số trong biểu đồ Collatz (Tín dụng cho @tsh để cho tôi biết về khái niệm này), được định nghĩa như sau: (sử dụng 2113làm ví dụ ):

Viết trình tự Collatz cho m(trong trường hợp này, 21):

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

Viết trình tự Collatz cho n(trong trường hợp này, 13):

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

Bây giờ hãy đếm xem có bao nhiêu số chỉ xuất hiện trong một trong các chuỗi. Điều này được định nghĩa là khoảng cách Collatz giữa mn. Trong trường hợp này 8, cụ thể là,

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

Vì vậy, chúng ta có khoảng cách Collatz giữa 2113như C(21,13)=8.

C(m,n) có các tính chất tốt đẹp sau:

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

Hy vọng định nghĩa của C(m,n)bây giờ là rõ ràng. Hãy bắt đầu săn trứng trong không gian Collatz!

Khi bắt đầu trò chơi, bộ điều khiển quyết định vị trí của một quả trứng Phục sinh, được biểu thị bằng tọa độ một chiều của nó: Một số nguyên trong khoảng [p,q](nói cách khác, một số nguyên nằm giữa pq, cả hai đầu bao gồm).

Vị trí của quả trứng vẫn không đổi trong suốt trò chơi. Chúng tôi sẽ biểu thị tọa độ này là r.

Bây giờ bạn có thể đoán ban đầu là 0 và nó sẽ được ghi lại bởi bộ điều khiển. Đây là vòng thứ 0 của bạn. Nếu bạn thật may mắn khi bạn đã đạt được nó ngay từ đầu (tức là 0 = r), trò chơi kết thúc và điểm của bạn là 0(Điểm càng thấp, càng tốt). Mặt khác, bạn vào vòng 1 và bạn đoán mới 1 , điều này sẽ tiếp tục cho đến khi bạn hiểu đúng, tức là n = r, và điểm của bạn sẽ là n.

Đối với mỗi vòng sau số 0, bộ điều khiển cung cấp cho bạn một trong những phản hồi sau để bạn có thể đoán tốt hơn dựa trên thông tin đã cho. Giả sử bạn hiện đang ở nvòng thứ và do đó bạn đoán là n

  • "Bạn đã tìm thấy nó!" nếu a n = r, trong trường hợp đó trò chơi kết thúc và bạn ghi bàn n.
  • "Bạn gần hơn :)" nếu C (a n , r) <C (a n-1 , r)
  • "Bạn đang đi vòng quanh quả trứng" nếu C (a n , r) = C (a n-1 , r)
  • "Bạn ở xa hơn :(" nếu C (a n , r)> C (a n-1 , r)

Để lưu một số byte, tôi sẽ gọi các câu trả lời là "Phải", "Gần hơn", "Tương tự", "Xa hơn", theo thứ tự được trình bày ở trên.

Đây là một ví dụ trò chơi với p=1,q=15.

  • a 0 = 10
  • a 1 = 11, trả lời: "Gần hơn"
  • a 2 = 13, trả lời: "Xa hơn"
  • a 3 = 4, trả lời: "Xa hơn"
  • a 4 = 3, trả lời: "Gần hơn"
  • a 5 = 5, trả lời: "Tương tự"
  • a 6 = 7, trả lời: "Phải"

Điểm : 6.

Thử thách

Thiết kế một chiến lược xác định để chơi trò chơi p=51, q=562với số điểm tốt nhất.

Câu trả lời nên mô tả chi tiết các thuật toán. Bạn có thể đính kèm bất kỳ mã nào giúp làm sáng tỏ thuật toán. Đây không phải là codegolf nên bạn được khuyến khích viết mã dễ đọc.

Câu trả lời nên bao gồm điểm số tồi tệ nhất mà họ có thể đạt được cho tất cả các trường hợp có thể rvà người nào có điểm thấp nhất sẽ thắng. Trong trường hợp hòa, các thuật toán có điểm trung bình tốt hơn cho tất cả các rs có thể (cũng nên được đưa vào câu trả lời) sẽ giành chiến thắng. Không có sự phá vỡ ràng buộc nào nữa và cuối cùng chúng tôi có thể có nhiều người chiến thắng.

Thông số kỹ thuật

Bounty (Đã thêm sau khi câu trả lời đầu tiên được đăng)

Cá nhân tôi có thể đưa ra một tiền thưởng cho một câu trả lời trong đó tất cả các dự đoán được đưa ra trong phạm vi [51,562]trong khi vẫn có điểm số thấp nhất hợp lý.


Bạn có một bộ điều khiển?
dùng202729

Không phải là một trong những giống như trong câu hỏi ban đầu.
Weijun Zhou

1
C (m, n) là khoảng cách của m, n trên biểu đồ Collatz .
tsh

Tôi đã tự mình nghĩ ra ý tưởng và không biết biểu đồ Collatz. Cảm ơn bạn đã nói với tôi rằng. Tôi sẽ bao gồm các thông tin trong câu hỏi.
Weijun Zhou

Câu trả lời:


5

Hồng ngọc, 196

Đây là cách khó hơn mà tôi nghĩ ban đầu. Tôi đã phải xử lý rất nhiều trường hợp tối nghĩa và kết thúc với rất nhiều mã xấu xí. Nhưng là rất nhiều niềm vui! :)

Chiến lược

Mỗi chuỗi Collatz kết thúc với một chuỗi các lũy thừa là 2 (ví dụ: [16, 8, 4, 2, 1]). Ngay khi gặp công suất 2, chúng ta chia cho 2 cho đến khi đạt 1. Hãy gọi công suất đầu tiên là 2 trong chuỗi pow2 gần nhất (vì đây cũng là công suất gần nhất của 2 với số của chúng ta bằng cách sử dụng Khoảng cách Collatz). Đối với phạm vi đã cho (51-562), tất cả các số pow2 gần nhất có thể là: [16, 64, 128, 256, 512, 1024]

Phiên bản ngắn

Thuật toán thực hiện:

  • một tìm kiếm nhị phân để tìm ra sức mạnh gần nhất của 2 với số hiện tại
  • một tìm kiếm tuyến tính để tìm ra mọi phần tử trước đó trong chuỗi cho đến khi số mục tiêu được phát hiện.

Phiên bản chi tiết

Đưa ra một trò chơi với số mục tiêu r, chiến lược như sau:

  1. Sử dụng tìm kiếm nhị phân để tìm ra sức mạnh của 2 gần nhất rtrong càng ít bước càng tốt.
  2. Nếu sức mạnh gần nhất của 2 được tìm thấy là giải pháp, hãy dừng lại. Nếu không thì tiếp tục đến 3.
  3. Vì sức mạnh của 2 được tìm thấy là sức mạnh đầu tiên của 2 xảy ra trong chuỗi, nếu theo sau giá trị đó đã đạt được bằng cách thực hiện thao tác (* 3 + 1). (Nếu nó xuất hiện sau thao tác 1/2, thì số trước đó cũng sẽ là lũy thừa 2). Tính số trước đó trong chuỗi bằng cách thực hiện thao tác ngược lại (-1 và sau đó / 3)
  4. Nếu số đó là mục tiêu, dừng lại. Nếu không thì tiếp tục đến 5.
  5. Với số hiện tại được biết từ chuỗi, cần quay lại và khám phá số trước đó trong chuỗi. Người ta không biết liệu số hiện tại đã đến bởi một hoạt động (/ 2) hay (* 3 +1), vì vậy thuật toán thử cả hai và xem cái nào mang lại một số gần hơn (như Khoảng cách Collatz) từ mục tiêu .
  6. Nếu số mới được phát hiện là đúng, dừng lại.
  7. Sử dụng số mới được phát hiện, quay lại bước 5.

Kết quả

Chạy thuật toán cho tất cả các số trong phạm vi 51-562 mất khoảng một giây trên PC bình thường và tổng điểm là 38665.

Mật mã

Hãy thử trực tuyến!

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}"

Ấn tượng. Có một điểm nhỏ: tôi tin rằng một trong những ý kiến ​​không nên nói "hình vuông hoàn hảo".
Weijun Zhou

1
@ WeijunZhou Bạn nói đúng. Đã sửa!
Cristian Lupascu

Bạn có thể nên bao gồm điểm số tồi tệ nhất cho tất cả các trường hợp, đó là 196.
Weijun Zhou

3

điểm kém nhất: 11, tổng điểm: 3986

Tất cả các dự đoán là trong phạm vi [51,562].

Thuật toán của tôi:

  1. Lần đầu tiên đoán 512 và duy trì một tập hợp các kết quả có thể vals, ban đầu tập hợp chứa tất cả các số trong phạm vi [51,562].
  2. Ở mỗi bước, hãy làm như sau:

    1. Tìm giá trị của đoán tiếp theo guesstrong phạm vi [51,562]như vậy mà, khi các giá trị trong vals(không bao gồm guesschính nó) được phân chia thành 3 bộ tương ứng với kết quả tốt Closer, SameFarther, kích thước tối đa của những 3 bộ là tối thiểu.
      Nếu có nhiều giá trị có thể guessthỏa mãn ở trên, chọn giá trị nhỏ nhất.
    2. Đoán giá trị guess.
    3. Nếu câu trả lời là "Phải", xong (thoát khỏi chương trình).
    4. Xóa tất cả các giá trị trong tập hợp valssao cho chúng không thể đưa ra kết quả đó.

Việc triển khai tham chiếu của tôi được viết bằng C ++ và Bash chạy trong khoảng 7,6 giây trên máy của tôi và cho điểm / tổng điểm kém nhất như được mô tả trong tiêu đề.

Thử tất cả các giá trị đoán đầu tiên có thể sẽ mất khoảng 1,5 giờ trên máy của tôi. Tôi có thể xem xét làm điều đó.


(P / S: Cho phép gửi mã không phải là mã. Nếu bạn không tin vào điểm số của tôi, chỉ cần tự thực hiện và xem)
user202729

Nhưng nếu bạn thực sự muốn thấy nó hoạt động mà không thực hiện lại vì một số lý do, hãy thử trực tuyến !
dùng202729

Đợi một chút tại sao tôi không thể để chương trình của mình đưa ra cây quyết định và chấm điểm: | nó sẽ nhanh hơn nhiều ...
user202729
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.