これはMobility Technologies (MoT) アドベントカレンダー6日目の記事です。
2022年10月22日に開催された企業・学校対抗の競技プログラミング大会の PG BATTLE 2022 にMoTから参加したので、その時出題された問題・感想について記載していきます。
PG BATTLE は年に一度行われる1チーム3人による企業・学校対抗の競技プログラミング大会で、出題されたプログラミングの問題を時間制限内に解いて提出するものです。2018年から開催されていますが、毎年1000人を超える社会人・学生が参加しています。
チーム対抗といっても、kaggleみたいに1つの問題に3人で取り組む、と言った方式でなく3人がそれぞれが「ましゅまろ」「せんべい」「かつおぶし」という難易度の問題に相談なしで取り組む方式になります。順位や勝敗は「3人が獲得した得点の合計」により順位が決まります。(同点だった場合は提出した時間が早い方が順位が上になります)
この大会は国内では有名な競技プログラミングサイトAtCoderと同様オンラインで参加することができます。利用できる言語・ライブラリがC++, Rust, PythonなどAtCoderと全く同じ環境で参加できます。AtCoderと異なる点は、大会が終了するまで自分の回答が合っているかどうかがわからずうっかりミスに気付けない点です(AtCoderでは自分の回答を提出したらすぐに合っているかどうかがわかります)。
昨年もMoTとして出場したのですが、昨年とは異なるメンバーで出場しました。(ex 昨年の参加レポート)
今年の参加メンバーは以下の通りです。
チーム名はオーソドックスに「Team MoT」でエントリーしました。
これから、それぞれの参加メンバーから出題された問題や感想について書いていきます。
ましゅまろを担当した水谷です。AtCoderのレートは緑です。競技プログラミングは3~4年前にやっていた時期がありましたが、ここ数年は殆ど参加していませんでした。PG BATTLE には谷本さんから slack で誘われて参加しました。今回の参加者の谷本さん、亀澤さんは自分よりレートが上なので、足を引っ張らないように自分解ける範囲の問題 (レート1200以下、PG BATTLE の難易度4相当以下) は落とさないようにしようという意気込みでした。結果は3問正解という想定通りの成績で、最低限の仕事はできたかなという思いでした。
高橋君の携帯電話番号は、090 で始まる 11 個の数字の列 で表されます。
先頭の 090 を 8190 に置き換えることで、 電話番号を国際電話用に変えることができます。
高橋君の携帯電話番号を国際電話用に変えてあげてください。
090を8190に置換すればOKです。文字列長が固定かつケースが限定されており、初心者であっても1問は正解させてあげたいという運営の意志を感じました。
個の整数と 個の記号( または )からなる数式 が与えられます。 この数式が正しいかどうか、すなわち で結ばれた式がそれぞれ等しいかを判定してください。
で分割された と で構成されたグループを作り、各グループの の和が同じであることを確認できればOKです。
人1, 人2,..., 人 の 人が試験を受けました。
試験は数学と英語の2科目からなります。 人 は数学で 点 英語で 点を取りました。
試験の成績は数学と英語の点数の重み付き和によって決まります。 具体的には、人の総合成績を と定義します。( は正の整数)
人 の順位を(人 よりも総合成績が真に高い人の人数) +1 と定義します。
人1の順位, 人2の順位,..., 人 の順位を順番に出力してください。
まずすべての人に対して得点を算出し、得点順でソートして順位をつけます。このとき元の順番は保持しておきます。次に元の人の並び方に戻してあげればOKです。同じ得点の場合は同じ順位になりますが、ソートするだけだとそうならないので、そこは気をつける必要がありました。
杯の蟹があります。 各蟹は10本の脚と1単位の蟹味噌で構成されています。 に対し、 番目の蟹の脚1 本の美味しさは 蟹味噌1単位の美味しさは です。
高橋君と青木君はこれらの蟹を以下の条件を満たすように分け合うことにしました。
条件を満たす分け方が何通りかを 998244353で割った余りを求めてください。
ただし、 ある2つの分け方は、ある1本の脚またはある1単位の蟹味噌であって、 一方の分け方では高橋君に、もう一方 の分け方では青木君に分けられているようなものが存在する場合に区別します。 脚同士や蟹味噌同士はたとえ美味しさが等 しくても区別されることに注意してください。
これは解けませんでした。本番で考えていたこととしては、1杯の蟹の分割パターンは で全列挙できるのでそれで条件に合う方法が探索でき、 杯の分け方を独立と考えるとそれぞれの結果を掛け算すれば良いという方針でした。しかし、これだとサンプルケースは通りますが、 杯の分け方は独立でない(それぞれの杯で考えると条件に合わないが、合わせると条件にあうケースが存在する)ので不正解です。
正解の解法ですが、まず分割パターンを工夫して減らす必要があります。同じ杯の脚の価値は同じなので、高橋くんと青木くんが杯の中身をいくつずつ分け合うかは11通り、どちらが味噌を持つかが2通り、 が最大で8なので、パターン数をとすることができます。ただこれでも制限時間に間に合いません。この問題では全体の総数は決まっているので高橋くんの方だけ見て値が合計値の半分の値になれば条件に合うことがわかります。すると探索範囲を2つのグループに分割する半分全列挙という手法が使用でき、結果パターン数をに減らせます。最後に、各パターンについて合計値が全体の半分に該当するか、該当する場合はそのパターンに対応する組み合わせが何通りあるか(例:高橋くんが脚を8本と味噌1個、青木君が脚2本と分けた場合、脚の分け方は)を考えることで、分け方の総数を制限時間内で算出することができます。
PG BATTLE は個人が別の問題を解く形式なのでチーム戦の要素は少ないのですが、解けないとチームとしての合計点が下がるため、普段一人で参加する時よりも緊張感があってよかったです。また、PG BATTLE の特徴として提出は一度のみという制約があるのですが、これも個人的には好きな要素でした。AtCoder だと提出が間違っていても時間にペナルティがかかるだけで再提出可能なので、こんな感じで通るかな?という確信度で提出することも少なくないです。しかし実務よりのことを考えると、確定前に想定されるケースを洗い出しておくことは重要なので、そういうことを考える練習にもなって良いかなと思いました。自分は今はアルゴリズムよりもヒューリスティックと呼ばれる長時間で1問を解く形式のコンテストに興味があるのですが、そちらでも複数人で1問の共通の問題を解く団体戦があっても面白そうかなと思いました。
せんべいを担当した谷本です。去年の10月あたりからAtCoderのプログラミングコンテストに積極的に参加し始めており、大会出場当時のレートは水色です(執筆している2022/12/4時点で青色にななっています)PG BATTLEは今回が初参加になります。
本番では別々に問題を解く、と言ってもやはりチームなので本番までに週1の90分間チームで練習してきました。AtCoderの過去問を使った練習や運営から提供されたPG BATTLEの過去問を使って練習してきました。これまでは自分一人で練習してきましたが、チームで練習することで「バグりにくいコードの書き方」をお互いに共有できたのはよかったと考えています。
肝心な本番ですが、4問中2問正解でした。練習では4問中3問解くとかできてたんですが、ケアレスミスで1問ロスしてしまいました。
枚のせんべいが一列に並んでいます。全てのせんべいは表裏が区別できます。全てのせんべいははじめ表を向いています。
あなたは 回の操作をします。 回目の操作では、左から 番目から 番目までのせんべいを裏返します。(裏を向いているせんべいを裏返すと表を向いた状態になるのに注意してください。)
回の操作を全て終えた後に裏を向いているせんべいは何枚ありますか?
長さ のbool型の配列を用意して、素直に 回目の操作では、 番目から 番目まで要素のbool値を逆転させていきます。最後に「裏」に対応するbool値の要素数を数えておしまいです。
いきなり難しい問題でなく最初はこういう簡単な問題でウォーミングアップできるのもPG BATTLEのよさでもあります。
ここに、 の番号がついた 個の食品があります。 番の食品の固さは であり、全ての食品の固さは互いに異なることが分かっています。加えて、以下のことも分かっています。
食品のうち「せんべい」であるものの番号を求め、昇順に出力してください。なお、答えが一意に定まることが証明できます。
問題文をよく読むと「 個の食品の中で 番目~ 番目に固い食品がせんべいである」ということがわかります。なので、 個の食品を固さで昇順ソートし、 番目~ 番目の要素を抜き出せた配列を出力すればOKです。
ただし、答えを出力する際に番号が昇順になっている必要があるので、抜き出した後の長さ の配列を番号について昇順にソートして出力する必要があります。
長さ の正整数列 が与えられます。
に対して、次の操作を好きなだけ( 回でも良い)繰り返します。
最終的な の要素の和としてあり得る最小値を出力してください。 ただし、最終的な の長さが の場合、要素の和は とみなします。
まず、stackを用意します。数列 の一番後ろの要素からstackに詰めいき、 をstackに詰めるタイミングで「」の連続した値の部分だけ stack からその部分を除く、という取り除き方が、求める最小値を出すための方法になります。数列 の要素を全て見ていった後、最後にstackの中にある要素の和が求める最小値になります。
この方法での計算量は になるので十分実行制限時間に間に合います。
このようにアプローチ自体は合っていたんですが、コーナーケースの処理でコーディングミスをしてしまい、不正解になってしまいました。
(問題文があまりに長すぎたので本質的な部分だけ抜き出しました。そのため問題のタイトルと噛み合っていないように見えますがご了承ください。問題文全て見たい場合はこちら。)
頂点の無向木 があり、頂点 には整数 が書かれています。頂点の集合 のスコア を次のように定めます。
について次の式を計算してください。
この問題はtwitterのタイムラインでも「鋼鉄せんべい」と揶揄されていたぐらいかなり難しかったです。
正解の解法はこのブログで伝えようとすると膨大な量になるためここではキーとなる用語だけ紹介します。詳細な解説は公式解説動画をご覧ください。
全方位木dpを使うところとconvolutionというところは思いついて実装しようとしたが、実装が間に合わず提出すらできずに終了してしまいました。(3. については思いつきもしませんでした。)
普段取り組んでいるAtCoderのコンテストと違い、「コンテスト中にケアレスミスに気づいて修正する」ということができないので、いつもより緊張感を以って取り組むことができました。結果的にケアレスミスしてしまい足を引っ張ってしまったのですが、「チームでプログラミングコンテストに出て戦績を残す」という点では達成できたのではないかと思います。
企業の部で出場したチームは188チームだったのですが、その中で38位というまずまずな成績を収めました。結果発表の際に上位10チームも発表されるのですが、点数だけでいくとケアレスミスさえなければもう少し善戦できてたのでは?と考えています。
次回、MoTでチームとして出場する際は、ケアレスミスを絶対にしないような練習とかを取り入れて臨みたいです。
かつおぶしを担当した亀澤です。今回PG BATTLEへの参加は4回目になります。いずれもかつおぶしを担当してきて今年こそ全完と思っていたんですが、結果としては1問目「等差数列」と3問目「模様」の2完で終わってしまいました。簡単に解法を説明します。また、Rust実装でのACコードはこちらです。
項数 で公差が正の等差数列 が隠されています。から と 以外の1項を除いた残りの項が順不同で として与えられるので取り除かれた項を求めてください。
まず の場合、取り除かれた項はになります。したがって、 と の平均が答えになります。
の場合、公差を求めることでに含まれない項を見つけることができます。公差は、 をソート後に隣り合う項の差分の最小値となり、 の最大値以外の各項に公差を足した値がに含まれていない場合、それが答えになります。
平面上に を頂点とする長方形の壁があります。長方形内部の始点 から時刻 に軸正の向きから度回転した向きに速さ1で等速直線運動し、壁に達するたびに鏡面反射します。時刻 に長方形内部の終点 に到達するような実数 の選び方が何通りあるか求めてください。
鏡面反射は折り返しと考えることで、折り返しを広げることで直進のみを考えればよくなります。今回の問題の場合、長方形とその内部の始点と終点を折り返したグリッドを考えることとします。具体的には下のような図を考えます(簡単のために一部の線のみ書いています)。
このように考えると、進む向きとして実数の代わりに折り返された終点の座標を選べば良いことになります。したがって始点から各グリッドの終点までの距離が となるようなグリッドの個数を数える問題になりました。x軸方向のグリッドの位置を固定すると、距離が となるような終点を含むグリッドがあるかどうか定数時間で判定できるため、計算量は となります。実装上は終点の位置が上下左右に反転した4種類のグリッドを考慮する必要があるので符号などに注意が必要です。
行列のグリッド(行列目のマスが)があり、各マスには0, 1のどちらかを書き込むことができます。0, 1からなる長さの文字列と長さの文字列からの各マスの値は次のように定められています。
個の整数組 について、 に書き込まれている値を求めてください。
愚直にを求めると計算量はになり、この制約では間に合わないため、工夫が必要になります。今回の場合、が十分に小さいため、の全てのグリッドではなく、与えられたグリッドのみ計算する方法を考えます。グリッド に対する, の直接の寄与を考えると二項係数を用いて次のように表すことができます(これはグリッド上を右か下に移動する場合の経路数と等価になります)。
あとは二項係数の計算についてですが、階乗について予め計算しておく(計算量)とそれぞれ定数時間で計算できます。また、今回は mod 2 での値さえわかれば良いのでLucasの定理を用いることで前計算なしで直接で求めることもできます(実装は公式解答例を参照)。
のグリッドがあり、上から行目、左から列目のマスをマス と呼びます。各マスは黒または白の色がついていて、マス(の色は として与えられます。このグリッドに以下の制約のもと、0個以上の駒を置くことができます。
0個以上の駒を置いた後のグリッドに対して、次のようにスコアを定義します。
考えられる全ての駒の置き方に対してスコアの総和を998244353で割った余りを求めてください。
これも考えられうる全ての駒の置き方は膨大(, の場合、およそ )になるため、ナイーブに求めることは難しいです。この問題では動的計画法(DP, Dynamic Programming)、特にビットDPと言われる方法を用いることで効率的に解くことができます。具体的には、「上から行目までみたときに、各列にすでに駒が置かれているかどうか表すビット配列がで、スコアがとなっているような駒の置き方の総和」 を考えます。次の行にどの列に駒を置くかという遷移を考えると配るDPの要領で場合の数を足していけばDPテーブルを埋めることができます。このDPテーブルを使うことで、最終的な答えを と求めることができます。
ただし、このDPをナイーブに実装すると となるため、制限時間に間に合いません。すでに駒が置かれた列には新たに駒を置けないという条件から全ての駒の置き方の遷移を行う必要はなく、実はで実装することができて、この計算量なら制限時間に間に合います。3乗という数字は各列ですでに駒が置かれたかどうかと、新たに駒を置くかどうか、の4通りの組み合わせのうち、すでに駒が置かれていて、かつ新たに駒をおく、というパターンを考える必要がないため、残りの3パターンだけの遷移を考えればよいことから導けます。
冷静に時間をかければ解けるような問題が多く、考察、実装の速度が求められるような問題セットになっていたと思います。2問目は単純に考察ミス、4問目は時間が足りずに解くことができませんでした。ただ、今回PG BATTTLEに向けてチームで練習する中で、フィードバックがなくてもミスをしないような意識や、実装上の工夫はチームで身につけることができたのではないかと思います。
最後に、明日のMobility Technologies アドベントカレンダー 7日目は大西哲也さんによる「MoTに入社して半年のデザイナーが語る会社の率直な感想と今後」です。お楽しみに!
興味のある方は 採用ページ も見ていただけると嬉しいです。
Twitter @mot_techtalk のフォローもよろしくお願いします!