スキップしてメイン コンテンツに移動

Project Euler - Problem 29

こんばんはSekia the Liarです。更新頻度についての釈明はさておきえーとP.E. 29でしたね。はい、すいません。

問題

  • 原文

    Consider all integer combinations of a^(b) for 2 ≤ a ≤ 5 and 2 ≤ b ≤ 5: (引用者による省略) How many distinct terms are in the sequence generated by ab for 2 ≤ a ≤ 100 and 2 ≤ b ≤ 100?

  • 日本語訳

    2 ≤ a ≤ 5 と 2 ≤ b ≤ 5について, abを全て考えてみよう: (引用者による省略) 2 ≤ a ≤ 100, 2 ≤ b ≤ 100 で同じことをしたときいくつの異なる項が存在するか?

解答

値の重複を取り除くにはハッシュを使うのが定石です。

use strict;
use warnings;
use feature qw/say/;
use Math::BigInt;

my %pows;
for my $n (2 .. 100) {
  for my $i (2 .. 100) {
    $pows{ Math::BigInt->new($n) ** $i } = 1;
  }
}
say scalar keys %pows;

しかしPerlのメソッド解決オーバヘッドは結構でかいので、10,000個のMath::BigIntインスタンス生成は割と時間を食います。毎回Math::BigIntというのも芸がないし、少し頭を使って解いてみることにしました。

ab = (an)b/nであることに着目しましょう。これは中学だか高校だかで習った通りです。ただし問題の範囲は整数なので、指数は2 ≤ b/n ≤ 100なる整数でなければなりません。つまりnはbの約数(ただしb自身を除く)です。

この等号で結ばれたべき乗は同じ(つまり重複した)値を持ちます。

例えば212 = 4(=22)6 = 8(=23)4 = 16(=24)3 = 64(=26)2 = 4,096であり、他に4,096となるようなべき乗は整数の範囲ではなさそうです(証明してないので間違ってたら教えてください)。

これをふまえると、等式が成り立つa、bの組のうちaが最小のもの(ここでa0、b0とおきましょう)が分かれば、残りのものはすべてa = a0n、b = b0/nの形をしているはずです。したがって同じ値を持つa、bの組はすべてa0、b0から求められます。

よってa0、b0のみを数えて、残りは重複として印をつけてスキップするようにすれば、重複のないべき乗の個数を数えることができます。

ところで2 ≤ an ≤ 100、2 ≤ b/n ≤ 100という拘束条件にちょっとした注意が必要です。というのも、n = 1のときにこの条件を満足せず、nがある程度大きくなれば満足するa、bが存在するからです。 このような数を取りこぼさないためにはb/n ≤ 100 × floor(logn100)(floorは床関数)までを探索し、b/n ≤ 100である間はnに1加えて何もせずスキップする、というような処理が必要になります。

#!/usr/bin/env perl

use strict;
use warnings;
use feature qw/say/;
use POSIX qw/floor/;

{
  my %memos;
  #returns divisors of $n (except $n itself) in numeric order
  sub divisors($) {
    my $n = shift;
    return @{ $memos{$n} } if exists $memos{$n};

    my @early = grep { $n % $_ == 0 } 2 .. floor sqrt $n;
    my @later = reverse map { $n / $_ } @early;
    shift @later if @later != 0 and $later[0] ** 2 == $n;

    return @{ $memos{$n} = [1, @early, @later] };
  }
}

sub logn($$) { my ($b, $n) = @_; (log $n) / (log $b) }

my %passed;
my $num_distincts = 0;
for my $n (2 .. 100) {
  my $max_i = 100 * floor logn($n, 100);
  for my $i (2 .. $max_i) {
    my $is_dist = 0;
    for my $d (divisors $i) {
      my ($m, $j) = ($n ** $d, $i / $d);
      next if $passed{$m, $j};
      next if $j > 100;
      last if $m > 100;

      $passed{$m, $j} = $is_dist = 1;
    }
    $num_distincts++ if $is_dist;
  }
}
say $num_distincts;

蛇足な補足

実装にちょっと珍しい多次元配列のエミュレート機能を使っています。$passed{$m, $j}という部分です。 ぱっと見ハッシュ・スライスのようですが、シジルが$なことから分かるようにスカラ値を返します。

これは次のように解釈されます:

$passed{ join($;, $m, $j) }

Perl 5のハッシュが文字列しかキーにできないという制約からこのような形になっているのでしょう。$;use Englishすると$SUBSEP)は特殊変数で、デフォルトでは"¥034"が入っています。これはUS-ASCIIではFS(File Separator)という制御文字なので、キーの値と混同されることはまずありません。

Pythonなどのタプルをキーとする方法に比べると美しくありませんが、使い勝手に差はないので案外と重宝します。疎な多次元配列を作るときなどは配列リファレンスよりこちらの方がいいでしょう。

コメント

このブログの人気の投稿

京大テキストコーパスのパーサを書いた

要旨CaboCha やなんかの出力形式であるところの京大テキストコーパス形式のパーサモジュールを Perl で書いたので紹介します。GithubTarball on Github Ppagesこれを使うと例えば CaboCha の出力した係り受け関係を Perl のオブジェクトグラフとして取得できます。使用例単なる文節区切りの例。#!/usr/bin/env perl use v5.18; use utf8; use IPC::Open3; use Parse::KyotoUniversityTextCorpus; use Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab; use Symbol qw//; my ($in, $out, $err); my $pid; BEGIN { ($in, $out, $err) = (Symbol::gensym, Symbol::gensym, Symbol::gensym); $pid = open3($in, $out, $err, cabocha => '-f1'); } END { close $out; close $err; waitpid $pid => 0 if defined $pid; } binmode STDOUT, ':encoding(utf8)'; binmode $in, ':encoding(utf8)'; binmode $out, ':encoding(utf8)'; my $parser = Parse::KyotoUniversityTextCorpus->new( morpheme_parser => Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab->new, ); say $in '星から出るのに、その子は渡り鳥を使ったんだと思う。'; say $in '出る日の朝、自分の星の片付けをした。'; close $in; my $sentence_trees = $parser->…

Algorithm::LibLinear の紹介

Notice: This article is outdated. Please refer an updated English tutorial. 要旨かなり前になりますが、Algorithm::LibLinear という Perl モジュールを書きました。CPANGithubこれを使うと線形分類器などが高速に学習できます。テキストや画像の分類が応用として期待されます。LIBLINEAR についてLIBLINEARLIBSVM と同じ台湾国立大学の Chih-Jen Lin 教授のチームが公開しているオープンソースの機械学習パッケージです。 関数のロジスティック回帰、サポートベクター回帰及び線形 SVM による多クラス分類を行うことができます。LIBSVM と違ってカーネル関数を使うことはできませんが、はるかに高速に動作します。Algorithm::LibLinear についてLIBLINEAR には C++ で書かれたライブラリと、その機能を使って機械学習と分類・関数回帰を行うコマンドラインユーティリティが含まれています。 Algorithm::LibLinear はライブラリの機能を Perl からオブジェクト指向的に利用できるようにした上で、コマンドラインユーティリティの一部機能をライブラリ化して Perl で再実装したものです。使い方分類問題を解くときは、訓練データセットの読み込み・スケーリング学習器パラメータの設定分類器の訓練実データの分類という手順で行います。訓練データセットの読み込み正解ラベルのついたデータを大量に用意して学習させます。LIBSVM 形式のデータを読み込むか:my $data_set = Algorithm::LibLinear::DataSet->load(string => <<'EOD'); 1 1:0.1 2:0.1 4:0.1 -1 1:0.1 2:-0.1 3:0.1 ... EOD HashRef として表現されたデータを使います:my $data_set = Algorithm::LibLinear::DataSet->new(data_set => [ +{ feature => +{ 1 => 0.1, 2 => 0.1, 4 =…

Perl 5 to 6 - 正規表現(またの名をルール)

これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 07 - Regexes (also called "rules")の日本語訳です。原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。Original text: Copyright© 2008-2010 Moritz LenzJapanese translation: Copyright© 2011 SATOH KoichiNAME"Perl 5 to 6" Lesson 07 - 正規表現(またの名をルール)SYNOPSISgrammar URL { token TOP { <schema> '://' [<ip> | <hostname> ] [ ':' <port>]? '/' <path>? } token byte { (\d**{1..3}) <?{ $0 < 256 }> } token ip { <byte> [\. <byte> ] ** 3 } token schema { \w+ } token hostname { (\w+) ( \. \w+ )* } token port { \d+ } token path { <[ a..z A..Z 0..9 \-_.!~*'():@&=+$,/ ]>+ } } my $match = URL.parse('http://perl6.org/documentation/')…