第30回全国高等専門学校プログラミングコンテスト競技部門に参加しました

はじめに

優勝しました。


開発について

 去年のリポジトリを参考にしつつ、汚くなったコードの書き直しも兼ねて新しいリポジトリで開発を始めました。 開発はLinux環境で例年通りC++とQtを使い、基本的なフォルダ構成などは去年と同じものを使う事にしました。


 開発初期に異常なやる気が生えるのはどこも恒例の事だと思うのですが、うちの部門もそんな感じでした。4/1のテーマ発表と同時にリポジトリ作成がされ、Visualizerの仮完成とゲーム進行部分の仮完成が4/3、各エージェント独立の簡単なBeamSearchができたのが4/4らしいです。


 基本的な部分が終了した後はcsv入出力関連の整備とかboostを使ったPythonとの通信とかロクに使わないアルゴリズムの整備とか適当な事をやっていました。5月から7月辺りでちょっと時間をかけて機械学習方面や強化学習をやってみたのですが、いやこれ無理でしょ、みたいな気分になって終わりました。

6月以降になるともう既にやる気を失っていて、8月辺りになると他の行事(SuperConとかJOI夏季セミナーとか)もあって自分はほぼ開発をしていませんでした。戦略を思いついたのは8月の後半あたりでした。

9月の前期期末試験が終わった辺りから流石に危機感を覚えたので、8月に思いついたものを実装して、通信部分を完成させて、10月に入った辺りで暇ができたので余った一週間ぐらいで通信確認+模擬対戦用のサーバーを実装しながら適当にバグ修正と本番向けの練習をして本番に臨みました。


 開発フローとか各自の担当部分なんですが、だいたい適当です。行動情報やフィールド情報のjsonへの変換部分とPythonを用いたサーバーとの通信部分と、あとはUIを中心としたちょっとした修正とか機能追加を他の人に投げたりしました。


アルゴリズムについて

NAPROCKがあるので一応それまで書かない事にしますが、だいたい他チームが想像している通りだと思います。


本選時の様子

比較的真面目な文体で書くのがつらくなってきたので、この辺から適当です

日曜日(本選1日目)

当日に組み合わせ発表があって、予行でもファーストステージでも仙台名取(去年の優勝、決勝でボロ負けした相手)と戦う事になった。実質決勝(?)

予行演習

うちの見立てでは大体半分とか6割ぐらいが通信失敗だろ〜みたいな感じだったんだけど、予行演習をすると7割とかのチームが死んでてびっくりした。
うちのチームは情報取得や通信に失敗する事なく動いたのでよかった。普通に動いて3勝とかすると微妙に注目される気がしたので、人力操作でエージェントを初期配置に戻して踏んだタイルも消す遊びをしてて、ちょっと楽しかった。

ファーストステージ

仙台名取がとてもこわかった。一戦目は相手が数ターン行動に失敗したりしてたから余裕があったんだけど、二戦目はこの大会中でうちが戦った中で一番負ける可能性が高かった気がする。(初戦での点差があったからある程度は負けても一位だったし、二位でも通ったからまぁ問題なかったけど)

月曜日(本選2日目)

前日の対戦結果からだいたいの組み合わせが分かっていたので、それらのチームのファーストステージでの様子とかを確認していた。連絡会議で決勝8エージェント10秒とか書いてあったのでマジかってなった。(8エージェント15秒になると思ってた)

セカンドステージ

前方に与謝野明子がいて面白かった。

準々決勝

ここから同時対戦がなくなって、1対戦を3人で見れるようになったのでけっこう楽になった。

準決勝

相手がすごい強くて、いい勝負だった。公開フィールドの方の勝負の終盤辺りが特に面白くてよかった。(いい感じに領域を潰しあえたので楽しかった)

決勝

非公開フィールド戦の辺りからカメラが後ろに回ってきて、いろいろ微妙な事にならないかなぁって言ってた。非公開フィールドが明らかに領域を取る前提の設計だったんだけど、最終的にはタイル点で数百点レベルの差が取れてよかった。

感想

今年も例年通り(特に事前の情報関連で)たくさんガバがあったと思うんだけど、まぁいつもと比べるとけっこうよかったと思う。

人力で勝った感じがあってとても申し訳ないんだけど、高専プロコンはアルゴリズムコンテストではないと思っているので・・・(人力コンテストです、ごめんね)


これは燃えそうな発言なんだけど、通信関連で失敗しない+30分で書けるビームサーチぐらいの強さがある 辺りが戦える最低限のラインだと思っていて、そうなっていないチームがかなり多かった気がする そもそもこの大会向けに時間をかけて開発しているチームが全然いないんじゃないかと思いました(それはそう?)(うちみたいに半年かける所って実際どのくらいあるんでしょうか、分からない)

あと機械学習とか強化学習の方面をやるチームがとんでもなく多かった、パンフレットを見たらだいたい半分ぐらいのチームがそうで、うちが上手くできてなくて実はめっちゃ強いものが完成する、とかだとアルゴ書いた人間(ぼく)の責任になってしまうのでこわかった。

AtCoderで黄色になるまでにやった事とかをまとめる

6/1に開催されたM-SOLUTIONS プロコンオープンで黄色になりました!やったー!

自分語り

塚本(@shibh308)です。今年のJOIでは平均以下の点数で本選落ちでした。これを書いている時点でのレートは2006で、今までのレート推移はこんな感じ(下画像参照)です。

f:id:shibh308:20190601234149p:plain

成績について

下に青になって以降のパフォーマンス遷移(AtCoder Performancesを使用させて頂きました)を貼っておきます。

f:id:shibh308:20190601234535p:plain


今年に入ってからのratedコンテストだと、500までが全部解けて600と700が半分程度、800と900が2割程度の正答率になっていました。(それ以上の配点の問題は解けていません)

今までに解いた問題について

AtCoderの公式コンテスト(ratedもしくはunrated企業コンテスト)の500点問題までを埋めて、600〜800をある程度と1000までをほんの少し解きました。
AtCoder ScoresAtCoder Problemsの画像だとこんな感じです。

f:id:shibh308:20190602000521p:plain
f:id:shibh308:20190602000915p:plain


あとはAOJ/AtCoder-JOIの画像のようにJOIの難易度6-9をある程度解いていますが、私はJOI勢ではないのでこれ以上埋まる事はないと思います。
f:id:shibh308:20190602001331p:plain


CodeforcesyukicoderCS Academyなども合わせると、解いた問題の合計がだいたい1400問ぐらいらしいです。


やったこと

AtCoderのコンテストに出ました。
まだレートが実力に追いついていない(と信じている)ので、ratedのコンテストに出ると平均してレートが上がります。 逆にコンテストに出なかったり、参加したコンテストがunratedになるとレートが上がりません。


おわりに

やがて君になる」「恋する小惑星」「ゆゆ式」あたりは黄色になるのによい影響を与えてくれたと思っています。

JOI2018/2019 春季トレーニング合宿 参加できませんでした記

はじめに

JOI本選で178点、Bランクだった。春合宿には落ちてしまった。

オープンコンテストがあるのでネタバレを見ずにそれを解いて、気分だけでも春合宿参加者に負けないようにしていく。できれば点数で春合宿erに勝っていきたい。


オープンコンテスト

1日目


試験(Examination)

x_i \geq a, y_i \geq b, x_i + y_i \geq cを満たすiの個数を各クエリ毎にa, b, cを変えながら数え上げる問題。

問題文を読んで、まずは小課題1を解く。これはO(NQ)で二重ループを回すだけで解ける。2点。 高速化をすればこれで100点を取れてしまうらしい(後で試してみたら2.5msで通すことができた)けど、流石に本番にそれをやる余裕はなかった。
小課題2はcが必ず0になる。成績とクエリを数学の点数の順に昇順ソートすると1つ目の式が消える。2つ目の式は座標圧縮+BITでよくあるやつをすれば解ける。ここまでで40分

満点解法ではその上で3つ目の式に対応できるようにどうにかしなければいけない。しかしこれは割と簡単で、BITの上に個数を載せていたのをやめて、代わりにBITの各要素をsetにしてx + yを追加、取得クエリではsetの要素内でc以上のものの個数を数え上げればよい。setでは最後の部分ができないので平衡二分探索木を使うなりさらに座圧+BITをするなり、をすれば解ける。ここまでで1時間。

かなり上手く考察が進んだと思っていたのだが、春合宿会場ではこの問題の満点解法を13人が通していたらしい。恐ろしい。 自分が解けていること、正答者数が多い事から難易度11はなさそうで、BITの上にさらに何かを載せるという部分が難しいので難易度9でもなさそうだと感じた。

ナン(Naan)

有理数の上でケーキ分割問題を解くもの、らしい。本番ではケーキ分割問題の存在自体を知らなかった。

とりあえず小課題1を解く。N=2の場合、右から1人目、2人目の順番に取るか、2人目、1人目の順番に取るしかないので、両方のケースを調べる。
各人物毎に公平な分配だと感じる最大の幸福度が決まっていて、それより大きな幸福度が与えられるような大きさのナンを貰っても意味がない事に気づくと、切られたナンを取る人の順番を決めた時の最適な切り方と公平な分配が可能かどうかがわかる。これを実装して5点。

これが分かると小課題2も階乗でのパターン全列挙で解ける事がわかり、やると合計29点が貰える。bitDPをすればもう少し人数が多くても対応できそうな気がするが、残念ながらそのような制約を持つ小課題はなかった。

少し休憩を挟んでいた事もあるが、これだけで実装に2時間以上を費やしてしまった。とても悲しい。 本番で100点を取れた人はいなかったようなので、どうやらこの問題の配点は29点らしい事が分かった。
難易度12だと思うのだけれど、典型といえば典型のようなので難しい。難易度12のマージテク問題が今となっては(難易度12の中では)かなり楽な方に分類されるように、15年後のJOI勢はこのような問題も典型問題として簡単に解けるようになっているのかもしれない。

ビーバーの会合(Meetings)

3点を指定するとその3点からの移動距離が最小になるような頂点の番号が返ってくるため、クエリ回数を一定以下に抑えながら木の構造を復元する問題。

この時点で残り時間が30分を切っている。何もわからないため、とりあえず小課題1だけを解く事にする。
頂点数が極端に少ないため、N ^ 3回クエリを実行して頂点番号を保存、ありえる木を全列挙して、それぞれシミュレーションする事にする。
流石に2 ^ {N ^ 2}個程度の辺それぞれに対して張るか張らないかを決めたらTLEを起こしてしまうため、木である事を使っていい感じにすると間に合う。実装は間に合わなかった。

0点だった。もし時間が十分に残っていたとしてもほんの少しの部分点しか取れなかったと思う。反省。
春合宿参加者の中でも0点の人は1人しかいなかったらしい。大失敗をしてしまった。何も分からなかったので難易度12だと思う。

まとめ

100+29+0=129点。1問目が解けたため良い点数にはなったと思うのだが、上手くやればあと数点は稼げたのではないかという気分になってしまう。
もし春合宿に参加していればおそらく得点で8位〜12位辺りに位置していると思うのだが、残念ながら春合宿には参加できていない。


2日目


ふたつのアンテナ(Two Antennas)

もう片方との距離が一定以上かつ一定以下なら通信できるアンテナが一定間隔に並んでいて、区間クエリが与えられるので区間内の双方向に通信できるアンテナの中で高さの差が最大なものを答える問題。

小課題1を見る。O(N ^ 2 Q)で解いてもいい事がわかり、これは前計算を何もしなくてもクエリ毎にその区間内のアンテナの組を全探索すればよい事がわかり、解ける。

小課題2を見る。全てのアンテナの組に対して、その2つが双方向に通信できるかどうか、通信できるならその高さの差(ここではスコアと呼ぶ事にする)をあらかじめメモする事ができる事がわかる。
求めたい物は f(l,r) = [l, r)にあるアンテナの組の持つスコアの最大値 なので、よくある区間DPをする事でこれを解く事ができる。

小課題3を見る。クエリが1つで、区間が全範囲を指している。
地点を左から順番に見ていく事を考える。ある地点が(片方向に)通信する事ができる区間のは右と左に2つあるが、右側のものだけを考える事にする。
そうするとSegtreeの区間取得クエリが丁度適用できるようになる。絶対値の最大値を計算するので難しいように見えるが、区間minを持つSegtreeと区間maxを持つSegtreeを両方用意してしまえばよい。
このままでは右側片方向の通信にしか適用できないが、地点xが左側と通信できる区間はx + A_x以上x + B_x以下区間になるので、Segtreeに対してx + A_xの取得が始まる前タイミングで要素追加、x + B_xの取得が終わった後のタイミングで要素削除をするようにすれば双方向に通信する事ができ、これで解ける。

初日の1問目と似た雰囲気を感じる、よい問題だった気がする。取れる部分点は全て取って、満点解法を諦めて他の問題に移るタイミングも適切でよかったと思う。春合宿の会場で同じ得点を取った参加者がかなり多かったはずで、おそらく難易度は12な気がする。

ふたつの料理(Two Dishes)

問題概要の説明をする必要がわからなくなってきたので、読んで。

小課題1はボーナスの値が正である事が保証されていて、ボーナスを得る事ができる境界の時間が全て一定になっている。
まずはかかる時間と得ることができる得点を累積和で保存しておく。
Naanの小課題1と同じように、カレーを先に作るか丼を先に作るかを決めたり、決め打ちをしながら累積和と二分探索を組み合わせたりする事で解ける。適当だが、これで5点を取る事ができた。

小課題3を考える。O(NM)でDPをするような見た目をしていて、また、達成したカレーの工程の個数と達成した丼の工程の個数からかかる時間がすぐに求まる事を考えると、累積和と組み合わせてよくあるDPをする事で解く事ができる。小課題2もこれが解ければ自動的に通るため、追加で10点。

実装を手早く済ませた上で自明な部分点を取る事ができたのはかなりよい動きだったと思う。満点が出ておらず、この問題もすぐ諦める事ができたのはかなり良かった。難易度はほぼ間違いなく12。

ふたつの交通機関(Two Transportations)

グラフが2つ与えられるので、2つを合体させたグラフの都市間の最短距離を求めよ。
独立なソースファイルを2つ用意し、その2つ間のやりとりはbool、なおかつやりとりの回数を一定回数以内に抑えなければいけない、間違ってもAtCoderには出せないとても面白い形式の問題。

小課題1を考える。これは与えられるグラフが1つなので、ダイクストラをするだけで解ける。答えの出力をできる関数がA側、グラフが与えられるのがB側なため、求めた最短距離を2進数に変換して送る必要がある。最悪ケースで距離を30bit管理すると落ちるので注意する。6点。

小課題2を考える。B側にある全ての辺の情報をA側に送信すればよいので、それをする。B側の辺の数が1000、辺の情報は頂点2つとコストがあるが、頂点情報には11bit、コストには9bit、合計すると1000 \times (2 \times 11 + 9) = 31000で、クエリ制限は58000回なので、これで8点を取る事ができる。

小課題3を考える。合体させたグラフが連結である事が保証されているので、これは木になる。
B側の辺のみを考えるとこれは自明に森になるので、あらかじめDFSをしておいてpar(x): 頂点xの親のようにすれば、A側に伝える時に必要な頂点情報の数はN個に抑える事ができ、小課題3が解けた。

小課題4以降を考える。まず、辺情報そのものを送るのではなくAとBのそれぞれでダイクストラをして、その結果をマージする事を考える。
そのままダイクストラをすると当然正しい結果が得られないので、「まだ確定していない頂点の中で、都市0との距離が距離が最も短い頂点」を考える。AとBからこの情報を取得し、そのうちの短い方を取り出す操作をN - 1回繰り返す事で、通常のダイクストラと同様に解く事ができる。
N - 1回の操作1回につき、距離情報に20bit、頂点番号情報に11bit、それをABそれぞれに対して行うので合計で (N - 1) \times (2 \times (20 + 11))回のクエリが必要となる。N \leq 900ならこれで解く事ができ、ここまでで38点を得られた。

ダイクストラの最短距離の更新に着目すると、前回の更新地点と今回の更新地点の距離の差はたかだか500以下になる事がわかる。そのため、その分の値を減らすことで 20bitかかっていたものを9bitに減らす事ができ、小課題6までを解く事ができる(本来9bitでよい部分を10bit取ると小課題6が通らなくなってしまうため、注意する)

ここまでをコンテスト中に考察して84点を得る事ができた。満点解法では、距離情報を送信した後に距離を比較し、距離が長い方からは頂点番号を送信しないという方法を取る。距離が短い頂点の情報だけ更新してしまえば、確定しなかった方の頂点の情報は更新をしなくていいというのが理由になる。
これをする事で、各クエリ毎に11bit減らす事ができ、満点を得る事ができる。

あまり得意なタイプの問題ではない印象だったが、無事84点を取る事ができてよかった。残りの16点についても本当にもう少しという所だったので、このような所を詰める事ができたらよいと思った。
難易度はだいたい10ぐらいだと思っているのだけれど、正答者数がかなり少ない気がする。

まとめ

35 + 15 + 84 = 134点。かなりの成功だったと思う。コンテスト終了直後は成功したのか失敗したのか分からない程度の印象だったが、他の参加者の点数分布を見た限りだと、どの問題も通すべき課題は全て通し、問題3については100点こそ得られなかったものの、なかなかの高得点を得る事ができたと感じた。
春合宿参加者全体の順位で見ても、これだけ取れていれば1桁順位はほぼ確実なのではないかと思った。今後もこのようなパフォーマンスを取っていきたい。


3日目


指定都市(Designated Cities)

問題概要は省略。

小課題1について考える。これは頂点の部分集合全てに対してその集合内の頂点全てを特別都市に指定した場合の答えを保存し、popcount毎に分類しておく事で解く事ができる。ほぼ自明枠。

小課題2について考える。まず与えられた木を適当な頂点を根とした根付き木とする。根の頂点のみを指定した場合、根から子の方に向かう辺のコストの総和が答えになる。
根の頂点以外を指定した場合について、選んだ頂点と根を結ぶパス上の頂点について、コストの値が根を選んだ場合と逆になる。そのため、根から選んだ頂点とのパスに含まれるコストの値を使って適当に計算して、木DPをすると解ける。

小課題3について考える。小課題2の時と同様に木DPをして片方の頂点を選び、もう片方の頂点とのパスに含まれる重みの総和の分だけさらにコストを減らす事ができ、それを実装すると点数が貰えるような気がしたが実装をする事ができなかった。

小課題4以降は全く分からなかった。小課題3すら実装できずに13点のみで、かなり酷い結果になってしまった。

ランプ(Lamps)

区間を選んで0に変更、1に変更、区間flipの3種類の操作をして、文字列AをBと一致させるための最小操作回数を求める問題。

小課題1について考える。全てのbitを0にするか1にするかをそれぞれ決めても状態数が十分少なく済むため、2 ^ N頂点でBFSをすると解ける。6点。

小課題2以降が全く分からなかった。ある程度の人数がこの問題を解いていて、6点しか取ることができなかったのはかなり悲しい。

時をかけるビ太郎(Bitaro, who Leaps through Time)

問題設定が面白かった。

小課題1について考える。これはO(NQ)で愚直にやってよいので、解く。制約がかなり大きいのでこれを高速化しても満点は取れそうになかった。残念。

区間取得、区間変更クエリが与えられる問題なため、Segtreeに載せる事を考える。f(x): xの時点で関数と対応する区間の始点に突入した場合の、区間の終点に出てくる時の最小スコアと時間の組 のような関数を考えると、Segtreeにはこの関数を載せて、関数同士のマージ動作を実装すればいい事がわかる。
想定解はこれで合っていたようだが、ここからの実装で大きな失敗をしてしまった。Segtreeに載せる要素が6つ(始点の最小時刻、始点の最大時刻、終点の最小時刻、終点の最大時刻、区間の長さ、区間のスコア)で、それらのマージ動作で6つの場合分けを実装しなければいけない事になり、他の問題が解けずに集中力が切れていた事もあり、実装ができずに破滅してしまった。

とても難しかった。厳しい。

まとめ

13 + 6 + 4 = 23点。本当に悲しい。通すべきだった部分点すら通せない、実装ができない、集中力が途中で切れてしまう、様々な所で失敗してしまった。
現時点で合計286点、初日と2日目のペースが続いていれば4日で500点を目指せたのに、大失敗をしてしまった。参加者全体の中ではおそらく15〜18位ぐらいだと思う。


4日目

24 + 0 + 6 = 30 かなしい


まとめ

4日合わせて129 + 134 + 23 + 30で316点だった。3日目と4日目が酷すぎる。残念ながら今年の進級はほぼ確定しているため来年のJOIには参加できない可能性が高いが、もし留年する事になったら来年の春合宿を目指して頑張りたい。

ABC121に参加したよ

はじめに

10:50で全完、ペナなしで12位だったよ
成功したのでコンテスト中の流れや考察についておはなしするよ


コンテスト中の流れ

コンテストが始まって、最初にDをみたよ 問題文が読めるまで7秒ぐらいかかった気がする(回線さん…)

D問題

問題文が分かりやすくていいね 区間[A,B]の値を全てxorした値を求めよって問題だよ

まずは制約を読むと普通にやると通らなさそうな事がわかって、こういうのは各bit毎に決めていく(ここ典型ポイント)といいんじゃないかな?みたいに思ったよ
でも各bit毎に決める時の判定がむずかしそう><(5秒ぐらい考えても分からなかったので、他の解法を考える事にしたよ)
問題文をもう一回読んで考えると、[A,B]の値は[0,A)と[0,B]の値のxorを取ればいい事がわかるよ(ABC007-D 禁止された数字 とか、桁DPでよくある考え方だと思う)
[0,B]は[0,A) xor [A,B]だから、こうすると[0,A)の部分が2回xorされて打ち消し合って、結果的に[A,B]だけ残るよ

で、ここまで考えるとf(x)=[0,x]の全てのxor を速く求められれば解ける事がわかるので、それをしたい でもわからないので実験をするよ(多分この辺で1分半ぐらい?)
愚直に600ぐらいまで全探索をして、"x: (ここに[0,x]の累積xorが入る)"みたいなのを600行出力したよ そうすると、明らかに4で余りを取った周期で何かが起きている事がわかるよ
具体的には、余りが0ならそのままxを返す、余りが1なら1を返す、余りが2ならx + 1を返す、余りが3なら0を返す をすればいい事がわかるよ なので実装をして、サンプルが会ったので投げると通った(ここまで5:41)
ちなみにこういうのは特に証明しないで投げてるよ 手元の実験で合っててサンプルが合ってれば通るみたいな気分だよ

提出した直後に順位表をチラ見すると400がまだ(ジャッジ待ちの僕を除いて)2人にしか解かれていない事が分かって、とても気分がよかった


C問題

問題文を読んで、適当に入力受け取り部分を書いたよ
値段が少ない順に買っていけばよいのはわかるんだけど、個数に制限がある → 値段と個数でpairを作ってそれを配列に入れて、それをソートして前から持っていけばよい みたいになったよ
Aiの制約がキツいので64bit整数を使う(intだとオーバーフローする)事に気をつけて実装、C++でpairの入ったvectorに要素を新しく挿入する時はemplace_backを使うとmake_pairとか書く必要がなくて便利だよ(setやqueueなど、他のデータ構造にもemplaceという関数があるよ)
これで通して終わり Dを提出してから2分20秒、考察+実装にかかったのがちょうど2分ぐらいだよ


B問題

問題文を読んで入力読み込みを書くことにするんだけど、すごい面倒な感じになってるよ
問題文はサラーっと読み流しつつ入力を読む、こういうのは最悪問題文を全部ちゃんと読まなくてもなんとなく書けばいいと思う…
「N個のソースコードのうち…個数を求めてください。」ってあるからN回ループを回して全探索をすればいい事がわかるよ(どうせABCのB問題なので、制約を読まなくても全探索でよさそうな事がわかる…)
あとはそのすぐ上の行にある式をそのまま実装、サンプルを合わせると通った Cを提出してから1:51秒 B問題にしては相当たいへんだと思う


A問題

とりあえず読むとよくあるやつな事がわかって、こういうのは答えもよくあるやつなので総和から引いて引いて重なった部分を足し直す(包除げんりみたいなかんじ?)といいと思うのでサンプルをを投げると合って、通ったよ これ普通に(H-h)*(W-w)で解けた事にコンテスト終わってから気がついたよ

おわり

ひさしぶりに成功して楽しかった。D問題で実験に走った割には速く解けて3位だったのは良かったんだけど、速い人は本当に速いんだなぁとおもった。
Dはf(b)-f(a-1)みたいに事に気づいて、あとは累積xorさえかければ実験ができて、この実験さえできれば天才じゃなくてもわかるので、実験しような!!!!

〜完〜