🐢

[鉄則B34] Game 7

2024/06/02に公開

https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_dg

Grundy 数列を求めたいが、山が大きい場合どうするか問題。

問題を要約する

  • 前問と同じ問題
  • ただし山が大きい
  • X, Y は 2, 3 固定

山が大きいとどうなるのか?

固まる。具体的には、まず Grundy 数列を生成するメソッドを用意する。

def grundy_by(rule:, max:)
  grundy = []
  max.next.times do |i|
    transit = rule.each_with_object([]) do |e, m|
      if i >= e
        previous = grundy[i - e]
        m[previous] = :found
      end
    end
    grundy[i] = transit.index { |e| !e } || transit.size
  end
  grundy
end

そして、

  • 2 or 3 個ずつ取れる
  • 最大の山の大きさは 8

の場合は、

grundy_by(rule: [2, 3], max: 8)  # => [0, 0, 1, 1, 2, 0, 0, 1, 1]

とすれば数列が求まる。これを前問に適用すると、

N, X, Y = gets.split.collect(&:to_i)          # => [2, 2, 3]
A = gets.split.collect(&:to_i)                # => [5, 8]
grundy = grundy_by(rule: [X, Y], max: A.max)  # => [0, 0, 1, 1, 2, 0, 0, 1, 1]
sum = A.inject(0) { |a, e| a ^ grundy[e] }    # => 1
ans = sum.zero? ? "Second" : "First"          # => "First"
puts ans

正しく解ける。ところが、今回の問題では、

  • 最大の山の大きさは 1000000000000000000

なので、

grundy_by(rule: [2, 3], max: 1000000000000000000)

として固まってしまう。したがって山が大きいと固まる。

最適化

ためしに数列を 100 まで生成してみると、

grundy_by(rule: [2, 3], max: 100)  # => [0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0]

部分的な数列の繰り返しになっているのがなんとなくわかる。5 個ずつ増やしてみると、

grundy_by(rule: [2, 3], max: 4)   # => [0, 0, 1, 1, 2]
grundy_by(rule: [2, 3], max: 9)   # => [0, 0, 1, 1, 2, 0, 0, 1, 1, 2]
grundy_by(rule: [2, 3], max: 14)  # => [0, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 0, 1, 1, 2]

わかりやすい。つまり 4 まで調べれば充分だった。だから、

grundy = grundy_by(rule: [2, 3], max: 4)  # => [0, 0, 1, 1, 2]

だけ用意して、たとえば 5 なら、

grundy[5.modulo(grundy.size)]  # => 0

丸めれば 0 が出てくる。

解説の模範解答風 (AC)

97 ms
N, X, Y = gets.split.collect(&:to_i)                            # => [2, 2, 3]
A = gets.split.collect(&:to_i)                                  # => [5, 8]
grundy = [0, 0, 1, 1, 2]
sum = A.inject(0) { |a, e| a ^ grundy[e.modulo(grundy.size)] }  # => 1
puts sum.zero? ? "Second" : "First"

2 or 3 個ずつ取れるときの数列が [0, 0, 1, 1, 2] になるのがわかっているためハードコーディングされている。

疑問

この問題は数列のハードコーディングで解けたけど、もし、2 or 3 個の部分が動的に変わる場合どうするんだろう?

参考

Discussion