AtCoder Beginner Contest 109の解説
A - ABC333
入力:
出力: が奇数となるような以上以下の整数が存在するならば'Yes'を、そうでなければ'No'を出力
まず、Cの値を掛けるとき、Cが偶数だとしたら、は偶数になってしまいますから、仮に出力が'Yes'となるならば、掛けるCの値はかです。
後はAとBの値に着目します。との値が与えられたとき、少なくとも1つの数が偶数ならば、この条件を満たすことはできません。よって、最終的にはとの偶奇を判定すればよいとわかります。
if((A * B) % 2 == 0) writeln("No"); else writeln("Yes");
感想: ごく普通のA問題でした。
B - Shiritori
入力:
制約は: 、は英小文字からなる長さ以上以下の文字列
出力: 高橋くんのどの発言もしりとりのルールを守っていたなら'Yes'を、そうでなければ'No'を出力
しりとりで大事なルールは2つあります。
- まだ発言されていない単語のみ使える
- 発言した単語の先頭の文字は直前の単語の末尾と一致する
この2つのどちらかが成り立っていなければ、しりとりは成立しません。
では、「まだ発言されていない単語のみ使える」をまず実装してみましょう。
今回の制約ではということもあって、わざわざ赤黒木を使う必要はありません。普通に配列を用意して、そこに発言の内容を格納しておき、発言の正当性を判定するときにはfor文で過去の発言とダブっていないか判定すればよいです。
string[] m; // 判定するときのコード(SはW_i) foreach(j; m) { if(S == j) { writeln("No"); return; } }
次に、「発言した単語の先頭の文字は直前の単語の末尾と一致する」を実装しましょう。
これは、毎回の入力ごとにその発言の末尾の文字を変数にもっておけばできます。
dchar last; // 判定するときのコード(SはW_i) if(last == S[0]) { last = S.back; } else { writeln("No"); return; }
また、のときは自由な発言ができるので、これも考慮して実装します。
auto N = readln.chomp.to!int; dchar last; string[] m; foreach(i; 0..N) { auto S = readln.chomp; if(i==0) { m ~= S; last = S.back; continue; } if(last == S[0]) { last = S.back; } else { writeln("No"); return; } foreach(j; m) { if(S == j) { writeln("No"); return; } } m ~= S; } writeln("Yes");
感想: 自分語りになりますが、実はコンテスト前の金曜日に友達としりとりをしながら下校していて、その後電車に乗っているときにしりとりの妥当性を判定するプログラム(まさにこれっぽい!)を考えていたので、これが出題されたときには驚きました。
C - Skip
入力:
出力: 全ての都市を度以上訪れることのできるの最大値
可能な移動は、
- 座標から座標に移動する
- 座標から座標に移動する
とあり、しかも移動は何度でもできるので、
- 座標から座標に移動する
という操作ができるといえます。
また、座標から移動を開始する、とありますが、利便性のためにすべての座標に関してで上書きします。
入力例で試してみましょう。
入力例1で上書きをしてみると、配列はとなります。
入力例2では、となり、
入力例3では、となります。
ここで着目すべきことは、それぞれの出力が、これら配列の要素の最大公約数になっている、ということです。
実際、上書きした配列を元に考えてみると、正整数の最大値を求める上で「可能なだけ一気に移動する」「全ての都市を1度以上訪れる」を成り立たせるためには、各要素の最大公約数を求めればいいことがわかります。
よって、コードは次のようになります。
auto ip = readln.split.to!(int[]), N = ip[0], X = ip[1]; auto x = readln.split.to!(int[]).map!(i => i - X).map!(i => i.abs); auto c = x[0]; foreach(i; x[1..$]) { c = gcd(i, c); } c.writeln;
感想: 最近はすぐに「これはgcdを使いそうな問題だな」とかそういう気づきをすぐに得られるようになってきたので嬉しい。
D - Make Them Even
入力:
制約は: 入力はすべて整数である、
出力: 偶数枚のコインが置かれたマスの数が最大となるような操作の列を次の形式で出力する
1行目のは操作の回数を表す以上以下の整数である。
行目には回目の操作を表す整数を出力する。
これは、マスにあるコインのうち枚を上下左右に隣接するマスに移動させる操作を表す。
まず、この操作はどのような順番でやっても結果には影響しません。入力例1で左上からfor文を回すように見ていき、もしもそのマスのコインの数が奇数ならば、上下左右に隣接したマスを見て、そのマスにコインを渡す、という操作をすればいいです。
また、出力のために、移動の情報は保持しておく必要があります。今回はContというTupleを用意しました。
alias Cont = Tuple!(int, "sx", int, "sy", int, "gx", int, "gy"); auto ip = readln.split.to!(int[]), H = ip[0], W = ip[1]; auto a = readMatrix!int(H, W); // オレオレライブラリにあります。下の提出URL参照 ulong cnt; Cont[] conts; auto dx = [1,0,-1,0], dy = [0,1,0,-1]; foreach(i; 0..H) { foreach(j; 0..W) { foreach(k; 0..4) { auto ry = i + dy[k], rx = j + dx[k]; if(0 <= ry && ry < H && 0 <= rx && rx < W && a[i][j]%2 != 0) { a[ry][rx]++; a[i][j]--; cnt++; conts ~= Cont(i+1, j+1, ry+1, rx+1); continue; } } } } cnt.writeln; conts.each!(i => writefln("%d %d %d %d", i.sx, i.sy, i.gx, i.gy));
感想: このコードはどの出力例とも異なった出力をしますが、それでもACします(それはそう)。最近の激ムズD問題からやっと開放された、そんな気持ちでいっぱいでした。
A: Submission #3151081 - AtCoder Beginner Contest 109
B: Submission #3165977 - AtCoder Beginner Contest 109
B(コンテスト中の赤黒木バージョン): Submission #3152791 - AtCoder Beginner Contest 109
C: Submission #3165996 - AtCoder Beginner Contest 109
D: Submission #3157410 - AtCoder Beginner Contest 109