【ruby】rubyで2進数を扱う
AtCorer Beginner Contest 147 C問題
毎度おなじみAtCorder Beginner Contest C問題でつまづいた。
解き方含めて学んだことをメモしておく。
問題のポイント
今回の問題では、正直者を1, 嘘つきを0で表記していく。
例えば3人が順に
[正直者, 嘘つき, 正直者] = [1, 0, 1]
[正直者, 嘘つき, 嘘つき] = [1, 0, 0]
といった形になる。
正直者と嘘つきの組み合わせは 2 ** 3で8通りあるわけだが、どうやって表記するかがポイントとなる。
ここで、eachメソッドとtimesメソッドを使うと一気に楽になる。
eachとtimesで2進数表記
3人の時は2 ** 3通り考えるのでn人の時はコレで8回繰り返せる。
(0..2 ** n - 1).each do |k| puts k # 表示されるのは0 ~ 7 end
コレを2進数化するにはtimesを使ってやる。
n = 3 (0..2 ** n - 1).each do |k| n.times do |j| # 数字kを2進数表記した時、n桁の数列のj番目の数字 puts k[j] end end # 結果出てくるのはこんな数字 # 0 0 0 1 0 0 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 1 1 1 # 3つずつくくるとこんな感じに2進数に変換される # [0 0 0][1 0 0][0 1 0][1 1 0][0 0 1][1 0 1][0 1 1][1 1 1]
解き方
とりあえず、入力する内容を受け取る準備をしておく。
n = gets.to_i # 全員が言ったことをまとめるsays配列を作成 says = Array.new n.times do i = gets.to_i # 各人が言ったとをまとめるwords配列を作成 words = Array.new i.times do person, truth = gets.split.map(&:to_i) words << [person - 1, truth] end says << words end
続いて、先ほどの2進数の考え方を使ってそれぞれのパターンで成立するかどうかを確認していく。
(0..2 ** n - 1).each do |mask| # まずは組み合わせが正しいと仮定しておく success = true n.times do |k| # 0は嘘つきなので評価せず次に飛ばす next if mask[k] == 0 # j番目の人が言っていることをピックアップ words = says[k] words.each do |person, truth| # mask[k]=1なので、wordsのtruthが正しくなければ仮定をsuccess = falseとする if truth == 1 success = mask[person] == 0 ? false : true else success = mask[person] == 1 ? false : true end # success = falseならそのあと調べる必要がない break unless success end end
コレでどの組み合わせが成立しているかがわかるので、最後に一番正直者が多い組み合わせを調べる。
普通の数字を2進数表記に変えるにはto_s(2)
を使ってやれば良い。
ans = 0 (0..2 ** n - 1).each do |mask| success = true n.times do |k| next if mask[k] == 0 words = says[k] words.each do |person, truth| if truth == 1 success = mask[person] == 0 ? false : true else success = mask[person] == 1 ? false : true end break unless success end if success # 成立していたら、2進数化して1の数を数えて、大きい方をansに置き換える。 ans = [ans, mask.to_s(2).count('1')].max end end
まとめ
今までrubyの問題で2進数を使った事がなかったのでがっつり引っかかってしまった。
プロコンだとよく使う手法のようなので、覚えておいて損はないはず。