7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AtCoderに登録したら解くべき精選過去問10問をMATLABで解いてみた

Last updated at Posted at 2020-05-03

はじめに

このツイートを見ました

2020年5月現在、AtcoderでMATLABは使えないので、使えるようになったら嬉しいな導入して欲しいなと思って応援のために書きました。

この記事で扱うプログラムのライブスクリプトはgithubの自分のリポジトリにあります。

参考記事

MATLABとは

MathWorks社が開発している数値解析ソフトウェア、およびその中で使われている言語の名称です。MATLAB - MathWorks
ディープラーニングや信号処理など様々なことができます。すごく便利。
学生の場合、学校が包括契約しているしているかもしれないので、確認してみてください。

AtcoderBiginnersSelectionとは

@drkenさんの記事、AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~ で紹介されたAtcoderの初心者向けの問題選です。問題だけ見たい人はAtcoderのページを見てください。

新しく言語を使ってみようと考えてる人がやってみるのにちょうど良い感じです。

環境

MATLAB R2020a

入力と出力について

入力

コマンドに入力例を貼り付けて実行することを想定します。

x = input('') % 数字, 文字列
str = input('', 's') % 空白含め一行分をstringで取得

scanf()はないのでinput(prompt)を利用します。

Pythonのように x = input()とすると引数不足で怒られるので注意してください。空白区切りはstrで取ってsplit(str)します。数字の空白区切りはstr2num(input('', 's'))str2double(input('','s'))すると大変楽になると思います。

現時点で分かっている問題点 (5月4日追記)

Octaveと比べ、MATLABはClikeな関数が使えません。よって大きな入力時にTLEする可能性がとても高いです。(Travelingのn=10000のときなど)
参考記事にあるようなscanf()は使えませんし、dlmread(filename)についてはMATLABに存在しますが現在非推奨です。
dlmread(filename)の代わりにreadmatrix(filename)という関数がありますが、どちらもfilenameに空文字/0/stdinなどは使えないためコマンドから入力を取ることができません。

つまり、input("")を多用するしか複数行を読み込む方法がないのが現状です。

この問題をどうにかしない限り、10000行の入力がある場合などで必ずTLEとなってしまいます。
知恵をください。

出力

適当にprint()するとFigureがプリンターから印刷されます。(なければエラー)

fprintf(formatSpec,A1,...,An)
disp(A)

formatSpecについてはこちら(MathWorks)を参照

公式の実装がどうなるか分からないですが、とりあえずfprintf()で答えを出すところまでを目指します。Octaveではprintf()でも大丈夫みたいですね。

PracticeA - Welcome to AtCoder

PracticeA.m
clear

a = input('')
secondLine = str2num(input('', 's'))
% secondLine = str2double(input('', 's'))
str = input('', 's')

fprintf('%d %s\n', a+sum(secondLine), str)

input('')では2/3piといった入力も受け付けます。(doubleでの処理になります)

str2num()

X = str2num(chr) は文字配列または string スカラーを数値行列に変換します。入力には、個別の要素を示すためにスペース、コンマ、セミコロンを使用できます。Mathworksより

だそうです。

これ以降もMathworksのリンクはたびたび出てくると思います。
MATLABの関数は公式ドキュメントにすべて記されているので、どのような引数が必要か戻り値はどうなっているのかは「matlab 関数名」と検索するとたいてい出てきます。
とても便利なので活用してみてください。

sum(A)はAがベクトルであれば総和を計算してくれます。Aが行列なら各列の和の行ベクトルが帰ってきます。sum(matrix, 'all')とかすると行列の要素の総和が帰ってきます(2018b以降)

ABC086A - Product

ABC086A_Product.m
clear

inputLine = str2num(input('', 's'))

c = prod(inputLine)

if mod(c, 2) == 0
    fprintf("Even\n")
else
    fprintf("Odd\n")
end

prod() はベクトルの要素の積を返してくれます。

if ~ elseif ~ else ~ end は見やすさのためにインデントを入れていますが、MATLABでは必ずしも必要ではありません。

余りは mod(a, b) で求めます。 a % b ではできません。MATLABでは % はコメントアウトです。Mathworksのmodのページの詳細に定義があります。

ABC081A - Placing Marbles

ABC081A_PlacingMarbles.m
clear

str = input('', 's')
fprintf("%d\n", count(str, '1'))

count(str, pattern) はstr内のpatternの出現回数を返します。str, patternはともに行列でも大丈夫です。

ABC081B - Shift only

ABC081B_Shiftonly.m
clear

n = input('')
a = str2num(input('', 's'))

answer = 0

while mod(a, 2) == 0
    a = a / 2
    answer = answer + 1
end

fprintf('%d\n', answer)

while ~ end はそのままです。whileのあとの文が真であれば継続します。

mod(a, 2) では1×nの行列が戻ってきます。これらの値が0であるかを == 0 で比較し、すべて0であれば真となります。

ABC087B - Coins

ABC087B_Coins.m
clear

a = input('')
b = input('')
c = input('')
x = input('')

answer = 0
matrix = x - (0:a) * 500 + (0:b)' * 100
answer = sum(matrix >= 0 & matrix <= c*50, 'all')
fprintf('%d\n', answer)

MATLABでは(0:N)と書くことにより1×(N+1)のベクトルを生成します。またベクトルや行列に'をつけることにより転置したものになります。行ベクトルと列ベクトルを + 演算子で足し合わせると、行列ができあがります。Mathworksを確認されたし

matrix >= 0 & matrix <= c*50 では0か1のlogicalな行列ができます。
0は不適な組み合わせ、1は適当な組み合わせの時に発生するので、この行列の要素の総和が今回の答えとなります。

愚直な実装であると次のようになります。

for i = 0:a
    for j = 0:b
        for k = 0:c
            if x == 500*i + 100*j + 50*k
                answer = answer + 1;
            end
        end
    end
end

ABC083B - Some Sums

ABC083B_SomeSums.m
clear

inputLine = str2num(input('', "s"))

answer = 0;
for i = 1:inputLine(1)
    temp = i;
    ref = 0;
    while temp ~= 0
        ref = ref + mod(temp, 10);
        temp = floor(temp/10);
    end
    if inputLine(2) <= ref & ref <= inputLine(3)
        answer = answer + i;
    end
end

fprintf("%d\n", answer)

MATLABでは配列へのアクセスは1から始まります。

~= は不等価を表します。

ABC088B - Card Game for Two

ABC088B_CardGameforTwo.m
clear

n = input("")
a = str2num(input("","s"))

b = sort(a,'descend')

aliceScore = sum(b(1:2:n))
bobScore = sum(b(2:2:n))

fprintf("%d\n", aliceScore - bobScore)

sort(A) はベクトルAを昇順に並び替えます。
sort(A,"descend")にすることで降順にします。

colon(:)についてはMathworksを参照。1:2:n はつまり [1, 3, 5 ..., 2*fix(n/2)]となります。
b(1:2:n)で奇数番目の要素を抜き出しています。

ABC085B - Kagami Mochi

ABC085B_KagamiMochi.m
clear

n = input("");
for i = 1:n
    a(i) = input("");
end

fprintf("%d\n", length(unique(a)))

MATLABでは行列の範囲外を指定して要素を挿入することができます。Mathworksの(行列の作成、連結、および拡張)を参照
ただし、サイズが大きくなるたびメモリを新しく確保するため、この方法は速度の低下の原因となります。あらかじめzeros(1,n)などでメモリを確保しておくと良いでしょう。

for文でinput()を多用していますが、この問題の制約で${n\leq100}$というものがあり、十分小さいのでTLEしません。

ABC085C - Otoshidama

ABC085C_Otoshidama.m
clear

inputLine = str2num(input('', 's'));

n = inputLine(1);
y = inputLine(2);

answer = [-1 -1 -1];

for i=0:n
    for j=0:n-i
        if y == 10000*i+5000*j+1000*(n-i-j)
            answer = [i, j, n-i-j];
        end
    end
end

fprintf("%d %d %d\n", answer)

この解法ですが、Octaveで実行するとおそらくTLEします。

n = 2000, y = 20000000 とすると、自分のPCでの計測ですが、MATLABで0.085932秒であり、Octaveでは9.70843秒となりました。これではあまりにも差がありすぎるため、MATLABユーザーがOctaveを使っての参加を躊躇してしまう理由になると思います。

ABC049C - 白昼夢

2020/05/05 変更 TLEしないように書き直しました。

ABC049C_Daydream.m
clear

s = input("", "s");
r_elem = ["dream" "dreamer" "erase" "eraser"];
r_len = [5 7 5 6];

s_length = strlength(s);

pos = s_length;
flg = 1;

while pos
    for e = 1:4
        if strcmp(r_elem(e), ...
                extractBetween(s, max(1, pos - r_len(e)+1), pos))
            flg = 0;
            pos = pos-r_len(e);
            break
        end
    end
    
    if flg
        fprintf("NO\n")
        return
    else
        flg = 1;
    end
end
fprintf("YES\n")

if strcmp(r_elem(e), ...に出てくる...ですが、これは引数などが多く、横に長くなってしまった時に改行したい場合に使います。
extractBetween(str, pos1, pos2)はstrのpos1からpos2までの文字列を抜き出す関数です。
returnで呼び出し元のスクリプトに戻ります。

TLEしてしまった解法

strlength(s)=10^5の時に10秒以上かかってしまいます。
上記のプログラムと考え方やアルゴリズムはほぼ同じですが、反転やint<->stringなどをする文字列操作が多いためTLEするものと考えられます。

ABC049C_Daydream.m
clear

s = input("", "s");

s_reverse = reverse(s);

r_elem = reverse(["dream" "dreamer" "erase" "eraser"]);

flg = 1;

while s_reverse
    for e = r_elem
        k = strfind(s_reverse, e);
        if ~isempty(k) && k(1) == 1
            flg=0;
            s_reverse = s_reverse(strlength(e)+1:end);
            break
        end
    end

    if flg
        fprintf("NO\n")
        return
    else
        flg = 1;
    end
end
fprintf("YES\n")

reverse(str)で文字列を反転します。
isempty()は空配列であれば真となります。

ABC086C - Traveling

nが大きくなるとTLEします。
意見求ム

ABC086C_Traveling.m
clear

n = input("");

txy = zeros(3,n+1);

for i = 2:n+1
    txy(:,i) = str2num(input("", 's'));
end

for i = 2:n+1
    diff_t = txy(1,i) - txy(1,i-1);
    distance = sum(abs(txy(2:3,i)-txy(2:3,i-1)));
    if mod(distance - diff_t, 2) || (diff_t < distance)
        fprintf("No")
        return
    end
end

fprintf("Yes")

txy(:,i) は txyのi列目のすべての要素を指しています。
よって最初のfor文によってtxyには次のようにデータが入ります。

txy =
     0     t1    t2   ...   tn
     0     x1    x2   ...   xn
     0     y1    y2   ...   yn

sum(abs(txy(2:3,i)-txy(2:3,i-1)))で2点間の市街地距離を求めています。

おわりに

久々にAtcoderを開きました(最近サボっているので‥‥) ~~たぶんACできると思いますが、TLEとかしませんよね?~~TLEするものがありました。申し訳ない
MATLABにそこまで詳しくないので、もっと良い書き方があったら教えてください。

また、今回調べる上で"GNU Octave"の存在を知りました。以外と動かせるのかなと思いましたが、for文を2重にするだけで時間がかかってしまうのはつらいですね‥‥
MATLABの代用にはなりませんね‥‥

MATLAB導入:pray:

7
3
2

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
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?