ACM プログラミングコンテスト 2004 国内予選開催

チーム名ばかり目立ってるようで。

例題は以下のURLだそうで。

ずいぶん簡単だなと思ったけど、Cのほうは下手すると計算量が爆発しますね。ちょっとrubyで解いてみる。

A.rb

def main()
  while (committees = scan_committees())
    puts committees.ok_date()
  end
end

def scan_committees()
  require 'scanf'
  size_line = gets()
  man_count, min_count = size_line.scanf("%d %d")
  return nil if man_count == 0 and man_count == 0
  commitees = 
  man_count.times do
    line = gets()
    dates = line.split()
    dates.shift
    commitees << dates
  end
  return Committees.new(commitees, min_count)
end

class Committees
  def initialize(commitees, min_count)
    @commitees = commitees
    @min_count = min_count
    @ok_date = 0
    find_date()
  end
  attr :ok_date
  
  def ok_date_list()
    ok_dates = 
    @commitees.each do |commitee|
      commitee.each do |date_text|
        date = date_text.to_i
        ok_dates[date] = 0 if ok_dates[date] ==  nil
        ok_dates[date] += 1
      end
    end
    return ok_dates
  end
  
  def find_date()
    ok_dates = ok_date_list()
    ok_max = 0
    date = 0
    ok_dates.each_index do |i|
      next if ok_dates[i] == nil
      if ok_dates[i] > ok_max
        date = i
        ok_max = ok_dates[i]
      end
    end
    @ok_date = date if ok_max >= @min_count
  end
end

main()

単純に配列に足して突っ込むだけ。そして条件に合う日付を見つける。速さだけを求めるなら、読み込みながら計算したり、中間結果を入れる配列をmapとかで工夫したりできるでしょう。

C.rb

def main()
  while (matrix = scan_matrix())
    secret_num = SecretNumber.new(matrix).secret()
    puts secret_num
  end
end

def scan_matrix()
  require 'scanf'
  size_line = gets()
  width, height = size_line.scanf("%d %d")
  if width == 0 and height == 0
    return nil
  end
  matrix = 
  height.times do |i|
    line = gets()
    scanner = "%c" * width
    matrix[i] = line.scanf(scanner)
  end
  return matrix
end

class SecretNumber
  def initialize(matrix)
    @matrix = matrix
    @coded = 
    @matrix.each do |line|
      @coded << Array.new(line.size, nil)
    end
    
    find_coded()
    @secret = seek_secret_num()
  end
  attr :secret
  
  def find_coded()
    @matrix.size.times do |x|
      @matrix[x].size.times do |y|
        next if @coded[x][y]
        find_coded_in_submatrix(x, y)
      end
    end
  end
  
  def seek_secret_num()
    secret_num = 0
    @coded.each do |line|
      line.each do |coded_text|
        if coded_text.size > 0
          coded_num = coded_text.to_i
          secret_num = coded_num if secret_num < coded_num
        end
      end
    end
    return secret_num
  end
  
  def find_coded_in_submatrix(start_x, start_y)
    return "" if start_x >= @matrix.size or start_y >= @matrix[start_x].size
    return @coded[start_x][start_y] if @coded[start_x][start_y]
    
    char = get_char_at(start_x, start_y)
    unless /\d/ =~ char
      @coded[start_x][start_y] = ""
      return ""
    end
    
    to_right = find_coded_in_submatrix(start_x + 1, start_y)
    to_bottom = find_coded_in_submatrix(start_x, start_y + 1)
    larger = max_as_num_text(to_right, to_bottom)
    @coded[start_x][start_y] = char + larger
    return @coded[start_x][start_y]
  end
  
  def get_char_at(x, y)
    @matrix[x][y]
  end
  
  def max_as_num_text(a, b)
    if a.size == b.size then
      a.to_i > b.to_i ? a : b
    else
      a.size > b.size ? a : b
    end
  end
end

main()

部分行列で各セルに対しての最大数を求めていく。各セルは隣接セルのすでにある結果を使って最大数を保存しておく。その部分行列をどんな手段でもいいので、すべてのセルのコード数が判明するようにもと行列まで大きくしていく。これも行列の保持の仕方や部分行列の拡大の仕方の工夫で多少早くできるでしょう。
全部のセルでのコード数が求まったら全部トレースして、その中で最大のものを取り出せばよい*1

部分行列での計算結果を再利用せず、各セルごとに最大数を探していくと単純再帰フィボナッチ数列みたく計算量が爆発するので注意。


追加:最大数を一緒に計算していく方法。やっぱりバッファは必要。最大数はバックトラック時に順次計算していけばよい。

def main()
  while (matrix = scan_matrix())
    secret_num = SecretNumber.new(matrix).secret().to_i
    puts secret_num
  end
end

def scan_matrix()
  require 'scanf'
  size_line = gets()
  width, height = size_line.scanf("%d %d")
  if width == 0 and height == 0
    return nil
  end
  matrix = 
  height.times do |i|
    line = gets()
    scanner = "%c" * width
    matrix[i] = line.scanf(scanner)
  end
  return matrix
end

class SecretNumber
  def initialize(matrix)
    @matrix = matrix
    @coded = 
    @matrix.each do |line|
      @coded << Array.new(line.size, nil)
    end
    @secret = 0
    find_coded()
  end
  attr :secret
  
  def find_coded()
    find_coded_in_submatrix(0, 0)
  end
  
  def find_coded_in_submatrix(start_x, start_y)
    return "" if start_x >= @matrix.size or start_y >= @matrix[start_x].size
    return @coded[start_x][start_y] if @coded[start_x][start_y] != nil
    
    char = get_char_at(start_x, start_y)
    unless /\d/ =~ char
      @coded[start_x][start_y] = ""
      return @coded[start_x][start_y]
    end
    
    to_right = find_coded_in_submatrix(start_x + 1, start_y)
    to_bottom = find_coded_in_submatrix(start_x, start_y + 1)
    larger = max_as_num_text(to_right, to_bottom)
    cel_code = char + larger
    @coded[start_x][start_y] =cel_code
    @secret = max_as_num(cel_code, @secret)
    return @coded[start_x][start_y]
  end
  
  def get_char_at(x, y)
    @matrix[x][y]
  end
  
  def max_as_num_text(a, b)
    if a.size == b.size then
      a.to_i > b.to_i ? a : b
    else
      a.size > b.size ? a : b
    end
  end
  def max_as_num(a, b)
    a.to_i > b.to_i ? a : b
  end
end

main()

*1:そのセルのコード数文字列だけじゃなく、その時点での最大数も一緒に返るようにデータ形式を定義すれば最大数検索は要らなくなる。たとえば[最大数, そのセルでのコード数, そのセルでのコード数の桁]のように。たぶんプログラムはわかりにくくなるだろうけど