第18回日本情報オリンピック予選の感想
概要
お疲れ様でした!ABC3完 + D15点 + E10点 + F6点 でした。合計331点。
本番の流れ
まずは22分でCまで解きました。Bで焦って15分使ってしまった。
そこから愚直にDを書いて、7点を取りました。とりあえず部分点を取っていくことにして、Eで10点まで取りました。
それから2時間半ほどDにつぎこんで、「二分探索か?」「キュー2つ用意してみよう」「DPではないだろうな」とかやって結局15点まで取り、終了直前にFで6点取りました。
A
愚直にシミュレーションをする。
B
実装が少し面倒でした。愚直にシミュレーションをしましょう。
C
マルとバツの列を左から見ていきます。最後に見たスタンプの形を覚えておきましょう。
マルバツスタンプを使って押したところから十分に離れているかどうかを判定して、そうであってかつ最後に見たスタンプと違う形をしているならば、使った回数をインクリメントします。
D
部分点(15点)でした。
与えられた配列をソートして、各要素から0を引き、そこから0より小さい要素を除外した配列を作ります。
それをforeachして、それぞれを海面の高さとし、島の数の最大値を出しました。(TLE)
E
部分点(10点)でした。
通りの飾りつけを考えられるので、これを全探索しました。(TLE)
F
部分点(6点)でした。
nextPermutationしました。これだと、同じ国の人が複数いるとき、彼らを一人一人区別することができません。
そのため、条件を満たす並びがあったときは、「与えられた配列のそれぞれについて階乗を計算し、それらをかけあわせた値」を場合の数として足しました。(TLE)
感想
AtCoderのレート相当の点数だったと思います。予選落ちでしょ。
Dの解法が思いつかなかったのが悔しいですが、「部分点を取るぞ!」という気持ちを忘れずにEやFも考えることができました。
この表を見てると「オア〜〜〜〜〜」という気持ちになりますが(TLの人つよすぎ)、逆にまだまだ伸びしろがあると考えるのがよさそうです(ポジティブになりたいね)。
ボーダーがどうなるのか非常に気になります。TLの人々は本選でも頑張ってくださいね!!
CODE THANKS FESTIVAL 2018の解説
A - Two Problems
入力:
制約:
出力: 高橋君の取りうる最大の得点
分かけて点
分かけて点 とれます。
それで、分の時間があります。
になっているので、まずは分かけて点取れないかを調べて、次に分かけて点とれないかどうかを判定すればよいです。
ulong res; if(T - C >= 0) { T -= C; res += D; } if(T - A >= 0) { res += B; } res.writeln;
感想: 普通のA問題。
B - Colored Balls
入力:
出力: 箱を空にできるなら'Yes'、そうでないなら'No'
制約:
とを比較して、小さい方は3を引き、大きい方からは1を引く操作を繰り返します。になったら、'Yes'を出力します。
操作の途中で、になってしまったら、'No'を出力すればよいです。
bool flag = true; while(flag) { if(X == 0 && Y == 0) { writeln("Yes"); return; } if(X > Y) { if(X - 3 >= 0 && Y - 1 >= 0) { X -= 3; Y--; } else flag = false; } else { if(Y - 3 >= 0 && X - 1 >= 0) { Y -= 3; X--; } else flag = false; } } writeln("No");
感想: 3WA。変に難しいことをしないで、単純に処理を書いたらACできた。
C - Pair Distance
入力:
出力: この街の交流コスト
制約:
は全て異なる
単純に必要なコストを計算すると、となり、最悪の場合にとなってしまい間に合わなくなってしまうかもしれません。
そこで、少し工夫を加えてみます。
まず、与えられたリストをソートします。そうすると、で計算されていたコストを、としてで計算できるようになります。
それと、は負の値もとりますが、後々の計算で不都合が生じるので、リストの最小値をリストのそれぞれの要素から引いておきます(そうすると最小値が0に揃い、計算するときに負の値を考慮しなくてよくなります)。
また、愚直に計算をするとTLEするので、累積和を使います。
とすると、交流コストはのとき
のようになります。
一般化すると、
のようになります。
これによって、で計算することができました。
auto N = readln.chomp.to!int; auto x = readln.split.to!(long[]).sort().array; auto m = x.reduce!min; x = x.map!(i => i - m).array; long[] arr = new long[](N); arr[0] = x[0]; foreach(i; 1..N) { arr[i] = arr[i-1] + x[i]; } ulong res; foreach_reverse(i; 1..N) { res += i*x[i] - arr[i-1]; } res.writeln;
感想: ちょっと難しい問題だった。
D - Concatenation
入力:
出力: 連結してになるような題意の文字列の個数の最小値
制約:
は英小文字からなる
「連結」とは、今ある文字列の末尾に新たに文字列をつなげることです。
与えられた文字列を先頭から一つずつ見ていきます。
元となるそれぞれの文字列には、先頭と同じ文字が複数含まれていない、とのことであるから、これと「先頭の文字がその文字列においてアルファベット順で最小」より「先頭の文字と同じまたはアルファベット順でそれより小さい文字」があった場合には新たな文字列を繋げる必要があります。
dchar now = S[0]; ulong cnt = 1; foreach(i; S[1..$]) { if(now >= i) { cnt++; now = i; } } cnt.writeln;
感想: これ絶対Cより簡単だった。200点で出てもおかしくなさそう。
AtCoder Beginner Contest 112の解説
A - Programming Education
入力:
または
制約:
は1または2
出力: のとき、'Hello World'と出力し、のとき、を出力する。
今回は珍しいことに処理の分岐をする必要がありました。
N = readln.chomp.to!int; if(N == 1) { writeln("Hello World"); } else { auto A = readln.chomp.to!int, B = readln.chomp.to!int; writeln(A + B); }
感想: やるだけです。問題文がヤバいなあって感じ。
B - Time Limit Exceeded
入力:
出力: コストが最小となる経路のコストを出力
制約:
まず、以内に帰宅できない経路については弾きます。この条件を満たすコストの中での最小値を出力すればよいです。
int[] times; foreach(i; 0..N) { /* 入力を読み込む。 */ if(t <= T) times ~= c; } if(times.length != 0) writeln(times.reduce!min); else writeln("TLE");
感想: 'TLE'のときのケースについて処理するのを忘れてしまうと
'WA'するので気をつけましょう。
C - Pyramid
入力:
出力:
制約:
はちょうど1つに特定される
このような制約や条件が複雑な問題は、全探索をすることを考えます。
具体的には、今回はの3つの値について全探索すればよいです。
foreach(posY; 0..101) { foreach(posX; 0..101) { foreach(H; h.reduce!min..h.reduce!min+201) { bool flag = true; foreach(i; 0..N) { auto dist = max(0, H-abs(posX-x[i])-abs(posY-y[i])); if(dist != h[i]) { flag = false; break; } } if(flag) { writefln("%d %d %d", posX, posY, H); return; } } } }
感想: コンテスト中に解けなかった。「基本は全探索」この言葉を忘れてはいけない。あと、実行時間の制約も見ておくべきだと思った。
D - Partition
入力:
出力: の最大値
制約:
サンプルにない例を見てみましょう。
としてみます。この場合、出力は65になります。
195を素因数分解してみると、となります。
ここで注目したいのは、が出力になっていることです。
でははどこにいったのか、と考えてみると、実はの場合が考えられることから、それぞれを1つか2つずつ取っていることがわかります。
よって、「N以上でMを割りきれる最小の数」を探して、その値でMを割った値を出力すればいい、という方針が立ちます。なお、この値をとすると、探索すべきkの範囲はで十分です。
ただし、このやり方だと、テストケースにはないものの、一番計算に時間がかかるような「Nが2でMが以下の素数」などのパターンだと多分間に合わなくなってしまいます。
ジャッジ側が少しだけ情けを与えてくれたためにACすることができました。
int k = N; while(k <= M / 2) { auto r = M % k; if(r == 0) { writeln(M / k); return; } k++; } writeln(1);
感想: 割と単純明快なコードでACすることができました。
C問題より簡単に感じましたが、chokudaiさん曰く「それはAtCoderで遊んでいる層が数学に強い傾向にあるためであって、一般的にはC < Dという難易度になっているのは間違いないはず」とのことでした。
確かに全探索することを思いつけば簡単だったのかも…?
A: Submission #3341345 - AtCoder Beginner Contest 112
B: Submission #3342324 - AtCoder Beginner Contest 112
C: Submission #3357731 - AtCoder Beginner Contest 112
D: Submission #3348620 - AtCoder Beginner Contest 112
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
AtCoder Beginner Contest 103の解説
A - Task Scheduling Problem
入力:
出力: i番目のタスク -> j番目のタスク でかかるコスト の最小値
というのは、数直線上に表すと2点間の距離と言い換えることができます。したがって、の最大値から最小値を引くことでこの問題を解くことができます。
(A => A.reduce!max - A.reduce!min)(readln.split.to!(int[])).writeln;
感想: A問題なのに問題文が難しかった。でも実装はA問題レベルだったなあ。
B - String Rotation
入力:
は英小文字からなる
出力: 操作(文字列の末端を先頭に持ってくる)を任意の回数繰り返してSをTに一致させられるなら'Yes'、そうでなければ'No'
素直に操作を繰り返せばいいです。(なので回操作を繰り返して一致するタイミングがあればYes)
auto S = readln.chomp, T = readln.chomp; foreach(i; 0..S.length) { string tmp = S[1..$]; tmp ~= S[0]; if(S == T) { writeln("Yes"); return; } S = tmp; } writeln("No");
上は素直に操作を繰り返す方法ですが、S同士を連結してSSという文字列(例: S="aba"のとき、SS="abaaba")を作り出し、その中にTが含まれるかを判定することでも解くことができます。
auto S = readln.chomp, T = readln.chomp; S ~= S; if(S.canFind(T)) writeln("Yes"); else writeln("No");
感想: SとTをsortしてS=TならYesでええやろ!wとか思ってたら全然違いました。ちゃんと考えることが大事。
C - Modulo Summation
入力:
出力: に対して、としたときのの最大値
入力例1を見てみると、がの最大値となっています。よく見てみると、それぞれの項で最大の値をとっていることがわかります。
では、ここでのはどういう意味を持つのでしょうか。問題ではとなっていますが、試しにの場合を考えてみましょう。そうすると…
となるのがわかります。ここで、このという値を正の整数で表せないか、と考えると、実はで表せるとわかります。実際、です。というわけで、求めるべき値は最終的にとわかります。以下で最大の5つの素数 をかけ合わせると という莫大な値をとり、これはぐらいです。unsigned long long(Dではulong)でも値を保持し続けることはBigIntを用いないと無理です。
auto N = ri; auto a = readln.split.to!(int[]); a.map!(b => b-1).sum.writeln;
実は、BigIntを使ってゴリ押しすることもできます。実際にmの値を計算してみる。
readln; auto a = readln.split.to!(BigInt[]); BigInt m = a.reduce!"a*b"-1; ulong tmp; foreach(i; a) { tmp += (m % i).to!ulong; } tmp.writeln;
ここでとしています。でもどちらでもOKです。
制約上、入力される正整数は3000個まで許容されており、fの最大値をとるmの最小値は14748桁になるようです。
(すべて互いに素だと仮定した場合、上のmの値2つは等しくなりますから、この主張は正しい…はず)
感想: ゴリ押しでも解けることがわかり、現代のコンピュータの性能はやっぱりヤバイなあと思った。
D - Islands War
入力:
制約は:
出力: 取り除く必要のある橋の本数の最小値
これは蟻本に乗っている区間スケジューリング問題に似ています。
橋を取り除くにあたり、最も少ない本数で済むようにするには、できるだけ東から切っていけばよいです。
となるようにソートして、foreachし、もし今までに切った地点より東側で「戦いの西側の国」が存在したら、「その戦いの東側の国の手前」で橋を切り落とす。これでACできます。
alias Task = Tuple!(int, "start", int, "end"); void main() { auto ip = readAs!(int[]), N = ip[0], M = ip[1]; Task[] tasks = new Task[](M); foreach(i; 0..M) { auto s = readAs!(int[]); tasks[i] = Task(s[0], s[1]); } tasks.sort!"a.end < b.end"; int d = 0; int res; foreach(t; tasks) { if(d <= t.start) { d = t.end; res++; } } res.writeln; }
感想: コンテスト中は「imos法とかUnionFindでいけそう」とか思ってて、解けませんでした。簡単めなD問題なので解きたかった。
A: Submission #2876592 - AtCoder Beginner Contest 103
B: Submission #2878868 - AtCoder Beginner Contest 103
B(連結方式): Submission #2890012 - AtCoder Beginner Contest 103
C: Submission #2877869 - AtCoder Beginner Contest 103
C(BigInt): Submission #2890307 - AtCoder Beginner Contest 103
D: Submission #2890508 - AtCoder Beginner Contest 103
AtCoder Beginner Contest 100の解説
A - Happy Birthday!
入力:
出力: 可能なら'Yay!', そうでなければ ':('
「隣り合う2切れのケーキを両方取る」というのは、AかBどちらかが8より大きければ発生することなので、
if(A > 8 || B > 8) { writeln(":("); return; } else { writeln("Yay!"); }
でいいです。
感想: Aなのに問題文が少しむずかしい。
B - Ringo's Favorite Numbers
入力:
出力: 100でちょうど回割り切れるN番目に小さい整数
アアアアアアアアアアアアアアアアアアアーーーーー。
とりあえず、のときとそうでないときで分けて考えよう。
if(D == 0) { if(N == 100) writeln(101); else writeln(N); } else if(D == 1) { if(N == 100) writeln(100*101); else writeln(100*N); } else { if(N == 100) writeln(100*100*101); else writeln(10000*N); }
感想: ABCをやってきて一番WAしたB問題。制約を読もう。双子が悪魔に見える。
C - *3 or /2
入力:
出力: 最大の操作回数
この問題、いかにしてを2で割れるのか、というところだけに注目すれば解くことができます。
一回一回の操作で「2で割れる要素のうち1つを決めて除算をし、それ以外は3倍する」ことをすれば、最大の操作回数となるようにできます。3倍しても、2で割れる回数は変わりませんので。
ulong cnt; foreach(i; a) { while(!(i & 1)) { i /= 2; cnt++; } } cnt.writeln;
感想: B問題より簡単に感じた。
D - Patisserie ABC
入力:
出力:
sortして、 というふうに6通りに分けてやればいいんじゃね?!とか思ってたらWAしました。その後いろいろ試すもWAWAWAのWA。方針が間違っている。
ということで、コンテスト中には解けなかったのですが、よい考え方があるので、それを紹介します。
まず、仮に今回の制約でが0以上だったと仮定すると、明らかに、ケーキ毎にの総和をとり、その総和が大きい順からM個とってそれらの和をとればよいとわかります。
ケーキの総和をと表し、数列を降順にソートしたとすると、和はで表されます。
次に、今回と同じ制約で、が負の値もとることがあるとしましょう。
ところで、はのとき、そうでないときであることはご存知かと思います。
したがってといえます。
よってですね。
だから、です。
要は、絶対値まわりの処理が面倒なので、最初から各ケーキについて八通りのありうる値のとり方を計算しておけば楽なわけです。
この八通りについて、それぞれのパターンでケーキの価値の総和をとっておきます。そうすると、その8つのうちの最大値が求める値になります。
alias Cake = Tuple!(long, "x", long, "y", long, "z", long[], "score"); void main() { auto ip = readln.split.to!(long[]), N = ip[0], M = ip[1]; Cake[] cakes; foreach(_; 0..N) { auto ip2 = readln.split.to!(long[]), a = ip2[0], b = ip2[1], c = ip2[2]; long[] score; foreach(i; [1,-1]) foreach(j; [1,-1]) foreach(k; [1,-1]) { score ~= a*i+b*j+c*k; } cakes ~= Cake(a, b, c,score); } ulong res; foreach(i; 0..8) { cakes.sort!((a, b) => a.score[i] > b.score[i]); res = max(res, calc(cakes[0..M])); } res.writeln; } ulong calc(Cake[] cakes) { long beat, deli, popu; foreach(i; cakes) { beat += i.x; deli += i.y; popu += i.z; } return abs(beat)+abs(deli)+abs(popu); }
感想: めっっっちゃ頑張ってsortでゴリ押ししようとしたけどダメだった。精進しなきゃいけないですね。
A: Submission #2674649 - AtCoder Beginner Contest 100
B: Submission #2682146 - AtCoder Beginner Contest 100
C: Submission #2677346 - AtCoder Beginner Contest 100
D: Submission #2689798 - AtCoder Beginner Contest 100
AtCoder Beginner Contest 099の解説
A - ABD
入力:
出力: なら "ABC"、 なら "ABD" と出力
auto N = readln.chomp.to!int; if(N <= 999) { "ABC".writeln; } else { "ABD".writeln; }
感想: はい
B - Stone Monument
入力:
出力: 積もっている雪の高さ
まず、 という数列を用意する。
int[] arr = new int[](999); arr[0] = 1; foreach(i; 1..999) arr[i] = arr[i-1] + i+1;
そして、 の値 を求めて、 ならば、 を出力すればよいです。
int d = b - a; foreach(i; 0..998) { if(arr[i+1] - arr[i] == d) { writeln(arr[i] - a); } }
感想: ちょっとだけ考察が難しいかもしれないけど、問題文から「隣り合った柱について、雪に埋もれてない部分の長さがそれぞれ与えられる」ので、これらの差をとれば何番目の柱なのかがわかって、同時に積雪量も求められるとわかります。
C - Strange Bank
入力:
出力: この銀行からちょうどN円を引き出すのに必要な操作回数の最小値
僕はDPでときました。漸化式は
という感じで表せます。 具体的に、 に入る の値は
が考えられます。
あとはDPするだけです。
int[] dp; int[] arr = [1, 6, 9, 36, 81, 216, 729, 1296, 6561, 7776, 46656, 59049]; int solve(int i) { if(dp[i] >= 0) return dp[i]; int tmp = int.max; foreach(k; arr) { if(i < k) break; tmp = min(solve(i-k)+1, tmp); } return dp[i] = tmp; } void main() { auto N = readln.chomp.to!int; dp = new int[](N+1); dp[] = -1; dp[0] = 0; solve(N).writeln; }
感想: 入力例3で「これ貪欲法じゃ解けねぇ…!ならばDPしかない!!」となり、DPで解きました。
D - Good Grid
入力:
出力: すべてのマスに対して感じる違和感の和のとりうる最小値
下の文章の添字は問題文と異なります。
素直に全探索を考えます。そうすると、まずはcの(x, y)成分でについて、3色を色から色までで考える必要があるので、それぞれの色をとおき、各マスについて条件を満たしているかどうかを調べ、それぞれについて適切な処理を行おうとすると、計算量がとなり、これは間に合いません。
そこで、まず前処理として色を一つ決め、についてループを回し、先に違和感をそれぞれの場合について計算しておきます。
auto arr = new int[][](3, C); foreach(a; 0..C) foreach(x; 0..N) foreach(y; 0..N) { arr[(x+y)%3][a] += D[c[x][y]-1][a]; }
次に、実際に3色を三重ループで回し、の場合に注意して違和感の最小値を取得します。
foreach(i; 0..C) foreach(j; 0..C) foreach(k; 0..C) { if(i==j||j==k||k==i) continue; res = min(res, arr[0][i]+arr[1][j]+arr[2][k]); }
そして、を出力すればよいです。
感想: コンテスト中は前者の超愚直方式で解こうとしてTLE。終了後にこの方法を知る。まだまだ競プロはわからないことだらけ。
A: Submission #2643843 - AtCoder Beginner Contest 099
B: Submission #2645854 - AtCoder Beginner Contest 099
C: Submission #2649430 - AtCoder Beginner Contest 099
D: Submission #2652709 - AtCoder Beginner Contest 099