LoginSignup
2
1

More than 3 years have passed since last update.

Ruby と Perl で解くAtCoder ABC 162 C メモ化による高速化

Last updated at Posted at 2020-04-13

はじめに

AtCoder の AtCoder Beginner Contest 162 の C問題がTLE寸前でしたので、復習を兼ねて投稿します。

お礼

オフィシャルの解説やネット上の解説・解答を参照して理解を進めております。
AtCoder さん、競技プロプレイヤーさん、ありがとうございます。

なぜTLE

ループを三重にしていることもありますが、Perl では、最小公倍数を求める関数が遅いことが要因と考えられます。

そこで、もっとプログラマ脳を鍛える数学パズル アルゴリズムが脳にしみ込む70問 にて再三紹介されているメモ化による高速化を行いたいと思います。

今回のC問題
AtCoder Beginner Contest C - Sum of gcd of Tuples (Easy)

TLE した Perl ソース

TLE.pl
use v5.26; # strict say state biwise
use warnings;

chomp (my $k = <STDIN>);
sub gcd {
  my ($x, $y) = @_;
  while ($y) {
    my $r = $x % $y;
    $x = $y;
    $y = $r;
  }
  $x;
}
my $ans = 0;
for my $i (1..$k) {
  for my $j (1..$k) {
    my $c = gcd($i, $j);
    for my $l (1..$k) {
      $ans += gcd($c, $l);
    }
  }
}
say $ans;

自作の gcd 関数を使用しています。
Mathモジュールにgcd関数が用意されていますが、更に遅くなることが分かっています。

Perl 自作gcd
実行時間 TLE

Ruby の gcd メソッド

Ruby では Integer クラスに gcd メソッドが用意されていますので、それを使用します。

rubygcd.rb
k = gets.to_i

ans = 0
1.upto(k) do |i|
  1.upto(k) do |j|
    n = i.gcd(j)
    1.upto(k) do |k|
      ans += n.gcd(k)
    end
  end
end
puts ans

毎度のことながら、Ruby のスッキリ感は凄いですね。

Perl 自作gcd Ruby gcd
実行時間 TLE 741 ms

Perl メモ化による高速化

perlmemo.pl
use v5.26; # strict say state biwise
use warnings;

chomp (my $k = <STDIN>);
my @memo;
sub gcd {
  my ($x, $y) = @_;
  return $memo[$x][$y] if $memo[$x][$y] > 0;
  my ($u, $v) = ($x, $y);
  while ($y) {
    my $r = $x % $y;
    $x = $y;
    $y = $r;
  }
  $memo[$u][$v] = $x;
}
for my $i (1..$k) {
  for my $j (1..$k) {
    gcd($i, $j);
  }
}
my $ans = 0;
for my $i (1..$k) {
  for my $j (1..$k) {
    my $c = $memo[$i][$j];
    for my $l (1..$k) {
      $ans += $memo[$c][$l];
    }
  }
}
say $ans;

ここでは、予め gcd の計算結果を配列 memo にメモしておき、配列の値として呼び出します。

Perl 自作gcd Ruby gcd Perl memo
実行時間 TLE 741 ms 408 ms

Ruby メモ化による高速化

rubymemo.rb
k = gets.to_i

@memo = Array.new(k + 1).map{Array.new(k + 1, 0)}
def gcdmemo(i, j)
  return @memo[i][j] if @memo[i][j] > 0
  u, v = i, j
  while (j > 0) do
    r =  i % j
    i = j
    j = r
  end
  @memo[u][v] = i
end
1.upto(k) do |i|
  1.upto(k) do |j|
    gcdmemo(i, j)
  end
end
ans = 0
1.upto(k) do |i|
  1.upto(k) do |j|
    c = @memo[i][j]
    1.upto(k) do |l|
      ans += @memo[c][l]
    end
  end
end
puts ans

上記 Perl メモ化による高速化と同じ手法で、自作の gcd メソッドを使用しています。

Perl 自作gcd Ruby gcd Perl memo Ruby memo
実行時間 TLE 741 ms 408 ms 437 ms

メモ化 凄い

まとめ

  • ABC 162 C の解法を改良した
  • メモ化の応用ができるようになった

参照したサイト
Rubyの配列で使えるメソッド、二次元配列の使い方

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1