使用 Ruby 解 Code Jam

这几天开始注意到 Code Jam,规则还不是很熟悉,做了两个题目练习一下。貌似只用提交答案,所以语言并没有限制。我就用 Ruby 试着解了个题目。

之前用过 Ruby 做过计算,Ruby 的计算能力实在堪忧,但是语法的表现力还是很令人满意的,简言之就是:写起来很爽,跑起来很慢,很慢。使用 JRuby 倒是可以快点,但仅仅是快一点点 :(

题目

APAC 2015 Round A #A Password Attacker

这是一个排列组合的问题,m 个字符组成长度为 n 的字符串 (m<=n),每个字符至少出现一次,求组合数目。

每个字符至少出现一次,则有 n-m 个字符可以是 m 个字符中的任意字符,使用递归穷尽 n-m 字符所有组合情况。每种情况下,第 i 个字符出现 xi 次,则有

x1 + x2 + … + xm = n

其中 xi >= 1

组合次数为

n! / (x1! xm!)

将每种组合数相加就能得到总的组合数目。

但是,但是,这只能解决 samll dataset,即使改写成 C++,还是不能解决 large dataset。 看来还有更好的解决方法。

Ruby 相关

文件读取

fin = File.open(filename)

读取一行 n,m

m, n = fin.readline.split.map(&:to_i)

非常方便的 map inject

@nums.map{|i| MMath.factorial(i) }.inject(&:*)

Ruby 中的 Array Hash 很方便

@nums = Array.new(m) { 1 }

@@factorials = { 0 => 1, 1 => 1 }

还有,不用关心Int大小的问题,自动转化成 BinInteger

代码

class Solution
  def ways(m, n)

    @m, @n = m, n
    @nums = Array.new(m) { 1 }
    @n_factorial = MMath.factorial(@n)

    trav(n-m) % 1000000007
  end

  def trav(k)
    if k==0
      @n_factorial / @nums.map{|i| MMath.factorial(i) }.inject(&:*)
    else
      (0...@m).map do |i|
        @nums[i] += 1
        result = trav(k-1)
        @nums[i] -= 1
        result
      end.inject(&:+)
    end
  end
end

class MMath
  @@factorials = { 0 => 1,
                   1 => 1 }

  def self.factorial(n)
    @@factorials[n] ||= n * factorial(n-1)
  end
end


fin = File.open('A-small-practice.in')
fout = File.open('A-small-practice.out', 'w')
t = fin.readline.to_i
i = 1
while i <= t
  puts i
  m, n = fin.readline.split.map(&:to_i)
  fout.write "Case #%d: %d\n" % [i,  Solution.new.ways(m, n)]
  i += 1
end
fout.close