MtSakaのブログ

競技プログラミングをします

JOI2023/2024 春季トレーニング 参加記

JOI2023/2024春季トレーニングに参加しました。結果から言いますと四日間総合8位で代表にはなれませんでした。

本選

正直結果が良くなかった。そのせいで参加記とかを書く気が起きていなかったので書いていないしもうあんま覚えてないので言うことがない。なんとか通過はした。

春合宿まで

難易度11を中心に解いていてあとは意図的に残していた2020/21春を四日間バチャをした。結構良かった。
春前の感触としては上位6とかにいて代表争いをしているだろうというイメージでいた。そういう場合のイメージトレーニングみたいなことをしていた気もする。

Day0

practiceでN=1000のワーシャルフロイドやるだけがずっとTLEして解決せずに終わりちょっと心配だった。(が、結局本番で定数倍で死ぬことはなかったのでまあヨシ)

ここから問題のネタバレがあります




Day1

結果から言うと9-0-12 計21点 同率24位(実質下から二番目)だった。
この日は絶望の一言。まだあまり思い出したくないのでちょっと忘れたころに詳細を書くつもりです。とにかく後半2時間の記憶が全くというほどない。
家に帰って雛鶴 あいみたいに大泣きした。

結果的に、心を入れ替えてDay2以降取り組むマインドに切り替えることができた。

Day2

boardgame

小課題1~4は丁寧に状態を考えて解くことができた(この時すでに自分の中ではO(N2/K)に落ちていた)
あとはK<=100さえ解けば平方分割で解ける!と思い考察するとプレイヤー2~Kのコストは傾き1,2の半直線であることには気づくがうまく言語化ができず後半の疲労感の中考察していたのもあり、迷走してしまいちゃんと詰められなかった。(反省)
結局小課題1~4だけの36点

tricolor

全くと言っていいほど考察が出なかった。こういうパズルは一生苦手だと思う。5点しか回収できなかった。頑張ったら15点はいけたなあと思ったが40,50点を取っている人が理解できない...

vegetables5

この日問題を見て一番解けそうだと感じた問題だったので一番最初に取り組んでいた。
小課題1~3を回収した後は難しい考察をあまりせずに20分~30分で  O(Nlog(N)log(A)) の満点想定を思いついた。
とにかく添え字を合わせるのが不快だが、添え字を合わせる以前にTLEが出て絶望した。結果的に他の問題を回収した後に最後に再び取り組んで、軽い O(Nlog(N)log(A))でもなんとか通せる小課題4だけ取れた。この後最後の5分とかで添え字を合わせた解が出来上がり、提出するがTLEしてかなりつらくなった。
解析で単調性を利用してlower_boundのlogを尺取りすることで落としたら満点が取れた。10分くらいしかかかってないので他の問題に割いていた時間をこれに割けば...と思ったがもう遅い。

36-5-67 計108点 Day2単体3位と割と良い結果となった。

Day3

collection

最初に見た問題で、初手の考察で  O(N^{3}M)区間DPに簡単に落ち、Nの次元を一つ落とせば一気に38点入るため頑張って区間dpの改善を考えた。考察をしたが大胆予想をするしかないと考え大胆予想をして実装するがWAで撤退した。結果的に最初の一時間をこれに浪費してしまった。

joitour

問題を真面目に考え始めた時点で時間が微妙に残っており、満点特攻するなら特攻するくらいの時間しか残っていなかった。HLDを使って根へたどっていって情報を更新すればいいことには気づいていたが、情報量が多すぎて頭の中でそれを管理する方法がうまく思い浮かばなかったため結局部分点をちまちま回収することにした。
部分点自体はそれほど難しくなく、丁寧に実装して52点を取れた。ただ、中途半端に時間が余った。

tower

この日の三問で一番解きやすそうに見えた。段数/時間のグラフを考えて非連続になる点を考えるという方針でやると区間を管理するテクだけで割と簡潔に書けた。ただこの考察にいきつくまで紆余曲折しており、実装も結構バグらせて時間がかかった。
何度も提出してなんかいつの間にか満点が取れた。ついに初の満点である。

11-52-100 計163点 Day3単体2位でDay2,3だけの成績だと1位だった。総合も8位まで舞い戻った。これでDay1で底辺にまで落ちた自信がちょっとだけ取り戻せた気がする。ボーダーとは80点以上差があったので厳しいなあと思ったがワンチャンを狙える位置ではあった。

Day4

escape2

ようやく実家が出てくれたという感覚になった。絶対に満点を取るという強い意志でダブリングを実装し、平方分割をして満点が取れた。ただ実装に無駄に時間がかかった。ちゃんと鍛えていたはずだが三日間の疲れが予想以上にあったのだろう。

island

面白いパズルで今までのどの日のcommunicationと比べても取りやすいだろうと思った。ただ、全然うまく線形回のクエリに落とせなかった。
結局クエリ数は(次数の最大値)*Nくらいしか実装できず50点。
人々の満点解法がきれいすぎてびっくりした。こういうのは解ききりたかった。

tabletennis

解の上界の評価をしてその結果をもとに3すくみの個数を頑張って山登りとかすれば解が割と高速に求まるんじゃないかと読んで実装したが評価からうまく行かず失敗してしまった。
最後20分くらいはなんとかN<=20を埋め込むでもして23点だけでも自明部分点以上を回収しようと思い、頑張ってコードを回したがそもそも間に合わなかった。

100-50-9 計159点 単体8位(多分) 総合8位(合計451)

結果的には昨年度と同じ順位まで追い上げることができた。すべてはDay1が悪いが、そこからの修正力には自信を持ちたい。
来年は代表になります。頑張ります。

【色変記事】AtCoder橙になりました

2023/2/18のARC172で橙になりました。ずっと目標だったので本当に嬉しいです!

自己紹介

・私立中高一貫男子校の高1
・中学入るまではscratch触ったことある程度
・中1の12月から競プロをやり始めた

黄色になるまで

↓これ mt-saka.hatenablog.com

橙になるまでやったこと

AtCoder上で1500問ぐらい解いた
・JOIの勉強
春合宿の勉強でかなり鍛えられた
・5hコンテストにたまに出る
頭を動かし続ける体力が得られた気がする
・Good NotesをiPadに入れてiPadで考察するようになった
図とかが描きやすくて考察が捗る

諸々

これから

本当はもっと深夜にやってるコンテストに出たりしたい。高校卒業するまでは我慢するしかないけど。
あとAtCoderのWriterもやってみたいです
もちろん目標は赤になることなのでこれからも頑張っていきたい!

ISUCON13に参加して再起動チェックでfailしました

はじめに

ISUCON13にチーム「ナニモワカラナイ」で出て再起動後のデータチェックで不整合があったためfailとなりました。
最後のベンチのスコアは83,615でした

チーム紹介

MtSaka: インフラナニモワカラナイ、Goを多少できるつもり これを書いている人
NaHCO3: 直前一週間の練習会でSQLを極めたN+1解消担当(重曹と呼ばれている)
hiikunZ: DBスペシャリストでインフラ含む全てを理解する人
自己紹介スライド供養

練習

ISUCON11 final,ISUCON9 予選の二つを走りました。
ISUCON11 finalはあまり振わなかった
ISUCON9 予選はかなり力を出しきれてもうボトルネックがアプリが使ってる外部APIだけみたいな状況になるまで突き詰めることができた。本番のスコアと比較したところ1位を優に超えるスコアを取れていました。ビックリ

本番当日

9:30

全員起床成功していた。
みんなでdiscordのボイスチャットに集まりYouTube togetherでライブ配信を見ながら今年の問題を楽しみに待っていた

10:00(開始直後)

重曹インスタンスを立ち上げ、hiikunZとMtSakaがマニュアルを読む

10:10

インスタンスが立ち上がった後Git管理などのすでにまとめてある初動をhiikunZがする。重曹とMtSakaでアプリのマニュアルを読みアプリの概要や仕様を確認する。

10:20

何もツールなどを入れてない状態でベンチ
スコアは3,896

11:15

hiikunZが初動を終わらせ、自分がpprofを入れてベンチを回す
スコアは3,462
明らかにstats_handler.goの全てが重いことがわかったのでSQLの改善を重曹に投げる
hiikunZはDNS関連の改善やサーバー構成いじりに走り、自分はtagsのオンメモリ化をすることにした。

12:50

hiikunZがDNSの改善と3台構成へのシフトと自分のtagsのオンメモリ化が終わる
スコアは3,806

13:00

重曹がuser_scoreおよびlivestreams_scoreの二つの新しいテーブルを作成し、それを用いてuserのstatisticsの高速化をした
と思ったらバグっていて、それをなおす作業が始まった

14:20

バグが直り、やっとベンチを走らせることに成功した。
スコアは4,028
そんなに上がらなくてちょっと落ち込んだ

14:45

重曹がusersの非正規化を行い、DarkModeを入れた
と思ったら再びバグって一旦revertすることに

14:50

自分はlivestream_tagsがinsert以外行われないことに気づいたのでそれをオンメモリ化した
と思ったらバグってrevert...

15:40

livestreamのstatisticsのN+1の解消を重曹とhiikunZがした
少しバグって時間がかかったがスコアが少し良くなった。
スコアは4,171

16:00

この時間になってようやく調子が乗ってきて怒涛の改善が入るようになった。
まずは重曹はiconのhashをusersのテーブルに組み込んだ
スコアは7,036
ブレイクスルーを感じた。

16:05

このブレイクスルーの時にmoderateとlivestreamsのsearchが重いことに気づきとりあえずmoderateで自明な改善を自分が入れたらmoderateはかなり改善したのでスコアは落ちたがそのままで行くことにした。
スコアは6,720

16:30

その後hiikunZがlivestream_tagsのオンメモリ化に成功して重曹がlivestreamsのsearchのSQL改善に成功する
少しバグったりしたが、うまくいき第二のブレイクスルーを経験する。
スコアは12,925

17:00

iconsが重いことに気づき、hiikunZが/iconsをDBで持たずにファイル保存する方式をとる。
自分はiconsの更新が2秒毎で良いことに気づいてfillLivecommentResponseを2秒毎にキャッシュ保存することにした。
スコアは21,478

17:30

fillReactionResponseも同様に二秒毎で良いことに気づきそれをキャッシュ保存した
スコアは48,684
これでいったん重かった部分が全部解消される。

17:32

livesreamのsearchのSQLのチューニングを重曹がしておりそれをする
スコアが伸び悩み少し不思議だった
スコアは49,277

17:45

実は貼られているはずだったINDEXが貼られていなくてそれに自分が気づきhiikunZがINDEXを貼る
重曹SQLチューニングがブッ刺さり、スコアが大幅に改善
スコアは81,286

17:55

pprofを自分が消し、hiikunZがslow-logなどを消して最終ベンチを走らせる
スコアは83,615

17:59

hiikunZがいきなりこれはfailします!とか言い出して怖くなった。
マニュアルを見てみると 負荷走行実行時にアプリケーションに書き込まれたデータが、サーバー再起動後に取得できない場合 と書いてあり、実装を見直すとtagsとlivestream_tagsのキャッシュが再起動後も動くような仕様ではないことに気づいて絶望した。

お祈りをしたが結局failであった。スコアで言えばtop30でかなり奮闘していたのでとても悔しい。

総括

良かった点

終盤の怒涛の改善とそれに伴うスコアの上昇はとても良い経験で実際いい感じに改善が回っていた。今までに味わったことのない改善速度でビビった。

悪かった点

インフラをまともにできるのがhiikunZしかおらず序盤でかなり負担がかかり、まともに改善を回せるまで時間がかかってしまった。来年以降は最後の一時間くらいの状況になるまでの時間を早くしたい。終盤でパラメーター調整をする時間がなかったりして少しもったいなかった。
また、まだ改善があるという状況だったのでやはりもっと序盤でスピーディーに諸々ができるようにしたい。

来年に向けて

自分はGoの一部知識不足だったり、インフラの無知さで迷惑を少しかけたと感じているのでそこら辺をある程度勉強したい。
来年の形式がどうなるかはわからないが、入賞したい。

JOI2022/2023春季トレーニング参加記

JOI2022/2023春季トレーニング(もう春合宿とは言わないらしい 悲しい...)に参加しました。その参加記です。

本選まで

mt-saka.hatenablog.com

↑の記事にある通り無事本選で優秀賞を取り春合宿進出しました。

本選後

自分は5hの経験があまりなかったから一年分のバチャは絶対にやろうと思い、去年の問題を意図的に避けてJOIの問題を解いて精進していた。
問題をとにかく解いて解ける感じの問題の感覚をつかんだり、ライブラリ類の練習に役立った。
バチャは実際に四日間かけて去年のばちゃをやりました。結構よかったので自信につながった気がする。
バチャを他にできないかなーと思ったときにoj.uzにJOI open contestの過去問があったので他のコンテストをやるよりこれをやったほうが良いだろうということで2021のバチャをした。こっちもそれなりにできて春合宿に向けて気持ちが高まっていた。

本選までのJOI精進記録
春合宿前日までの記録

表彰式(3/18)

表彰式は頑張って眠気に耐えた。
交流会では色んな人に会えてとても楽しかった。自分は羞恥心に負けてあまりツイッターを表に出さなかったため交流がそこまでできたとは言えなかったので来年は交流を頑張ります。
中高生以外だとけんちょんさんやつもいよろずさんと会えたのが結構嬉しかった。
JOI春勢は交流会を途中で抜けて筑駒の目の前にあるNTTデータセンター駒場に行った。全体の到着が少し遅れたので実機演習を15分延長してくれてありがたかった。
自分のキーボードがBluetooth接続モードのままでプラクティス中は永遠に有線接続できないと勘違いしていて、パソコン本体についてるキーボードが使いにくかったのでかなり萎えていたけど有線接続モードの存在に撤退ギリギリに気づいてなんとか使えることを確認出来て安心した。
トヨタコンのオンサイトとかぶっていてチューターらが結構いなくて個人的に面白かった。
トヨタコンの影響でABCが消えていたがその代わりにこどふぉのdiv2が生えていたので出た。途中で眠気に負けて翌日の準備だけして寝た。

Day 1 (3/19)

山手線に乗り換えるときに学校に行く感覚で逆方面に乗ってしまいそのことに気づくのに時間がかかったためかなり焦ったけど早めに家を出発していたのもあって普通に間に合った。
渋谷駅でkaageさんを見かけたけど電車で一緒になったら何を話せばいいかわからなかったので見なかったことにして別の車両に乗ってしまった。結局駅で見つけられたので色々話しながらNTTデータ 駒場研修センターまで歩いた。色々面白い話を聴けて楽しかった。歩いている途中後ろから全力疾走のsquare1001さんに追い抜かれてちょっと面白かった。

競技

ふたつの通貨 (Two Currencies)

問題文を読んですぐ並列二分探索が見えたので頑張ってオイラーツアー,BIT,Sparse Tableを書いて1時間20分くらいでACした。(オイラーツアーとRMQを組み合わせたLCAをバグらせて20分くらい失ったのがかなりもったいなかったけど)

JOI 国のお祭り事情 2 (Festivals in JOI Kingdom 2)

とりあえず自明枠10点を通してN<=30は頑張ってDPの状態を持てば5乗か6乗に落ちそうだとわかったので残り一時間とかでやることがなくなったらやろうと思いいったん次の問題に行った。(結果書かなかった)

パスポート (Passport)

とりあえずちょっと考えて最小値なのでグラフに帰着したらいけるかなと思ったら46点が簡単に取れた。そっから辺を逆にすると54点に伸ばせた。
こっから方針転換して1とNに行けるようにするという問題にうまく帰着できていい線まで行ったけどちょっとした嘘にはまって渾身の提出が1ケースだけWAだった。この1WAを解消して満点を取れると思い終了までの1時間粘着し続けたが結局通らなった。

結果:100-10-54 計164 で同率7位だった(同順位が4人いてしかも全員同学年だった。面白い)
最後の問題は本質がほぼ見えていたので通せなくて悔しかったが順位はまだ耐えていたため安心して帰宅できた。翌日もがんばろうとやる気がさらに湧いてきた。

トヨタコンのオンサイトコンテストの影響で日曜日なのにABCがあった。ABC Tournamentの対象だったので眠かったがとりあえず出た。
40分くらいで7完してExはJOIにはいらない数え上げだったので考えず寝た。

Day 2 (3/20)

この日はなんとか山手線への乗り換えに成功して早めについた。4着だった。

競技

ベルトコンベア (Belt Conveyor)

小課題1,2は一本ずつ辺を特定するだけ(15点)。小課題3の10点はmod 3かな~と思って頑張ったらとりあえず5回に収まって通すことができた。これだけに1時間くらいかかってしまい、Communicationのデバッグが苦手だと感じた。満点解法は直線を定数回でいけるからHLDしたときのlight edgeだけどうにか特定できんかな~と思いながら永遠に考えた。結局この方針の嘘にはまり最後の1時間半以上は実装していた。結局終了9秒前に出した提出は落ちてた...
想定解法と全然違っていて悲しい気持ちになった。

議会 (Council)

最初に見た問題だった。パっと見たときM<=20の制約できっと高速ゼータ/メビウス変換とか色々がんばるんだろうと思った。
とりあえず満点とりたい気持ちになり色々条件を整理したりしたらいい感じに高速ゼータ/メビウス変換に帰着できたので実装を始めたが途中で最大値評価をミスって迷走してmodintを実装したり、帰着できていたと思っていたものが少し嘘だったりと紆余曲折あったが無事開始1時間20分で満点を取れた。

水ようかん 2 (Mizuyokan 2)

Bを読んだ後すぐ読んでいた。このときに小課題1,2の15点分がわかっていた。AとBが両方ともいったん一息ついた後に取り掛かった。小課題3以降では絶対いい性質を見つける必要があるということがわかったが谷を一個にしていいというのがなかなか思いつかなくて昼食会場で言われるまでわからんかった。

結果:25-100-15 計140 二日間で304点で暫定8位。
昨日から何も進捗がなく、ボーダーが少し離れただけだった。このままではいけない

Day 3 (3/21)

朝、渋谷駅でyuto1115さんを見つけた気がしたが自信がなかったので話しかけられなかった。結局正しかったらしく話しかければよかったな思った。

競技

ビーバーの合唱 (Chorus)

パッと問題見て40点まではAを並べ替えるという視点に立つと自明だな~と思い、どうせ二乗はMongeとか使うんだろうなと思ったが結局わからず。40点はとりあえず実装して通した。

クッキー (Cookies)

一番何も見えなかった。箱を決め打つのはそれはそうだけど大きいほうから決めていったりその他必要十分条件の列挙は頭よいなあという気持ちになった。結局自明dpをがんばるだけの小課題1,2,3だけ取って25点。

旅行 (Tourism)

C上の区間で隣同士のパスのの和集合の頂点数だなあというのは見えていたので小課題1,2はいい感じにやるだけで小課題3は区間min/maxなのでセグ木。小課題4はHL分解して遅延セグ木で区間更新/区間総和をできれば良くてそれを書いた。これで35点、方針的にはミスではなかったっぽい。最初に考えてた満点方針が完全な嘘でこれに2時間以上費やしていたので悔しい。一番解ける可能性があった問題だった。

結果:40-25-35 計100 三日間で404点で暫定8位
上位には差をつけられ代表が遠のいたただけだったうえ、一個下のCyanmondには差を詰められ2点差になっていた。個人的にはばうまくいくことがなさそうなセットだったのが唯一の救いだった。

Day 4 (3/22)

朝渋谷駅で昨日のAのMongeとかの性質を使った高速化を頑張って読みたいなと思い記事を読み始めたら降り過ごしてしまった(それはそう)。結局理解できていまま会場に行った。

競技

最後の戦い (The Last Battle)

正直8点しかまともな解法が思い浮かばなかった。この四日間で一番何も考えていない問題だと思う。考え方がかなり下手だった。2桁点数も言っていないのがかなり恥ずかしい。

警備員 (Security Guard)

問題を見てQ=0は通したいなと感じた。パスで値がすべて1,2の場合は割と単純に解けたが、値が自由の場合はかなり大胆な予想をして実装した。結果通ってかなり興奮した。この予想をもとに木にも帰着してなんとかAC。一般グラフについても拡張できるだろうということで色々考えたが終盤で思考力が落ちていたのもあって嘘にハマって通すことができなかった。解説では自分がした大胆予想をもっと定式化していて、負けたなあと感じた。

ビ太郎の旅 (Bitaro’s Travel)

問題文を読んで一番の良心枠ではあるだろうという予想はついた。小課題1,2はシミュレーションそのままだが、そこからdpを考えて単調性を発見できないか1時間以上試行錯誤したが結果何もわからなくていったん小課題1,2を実装した。実装している最中に小課題3の制約の意図を理解しこれは向きの変更回数に着目する問題であるとやっと気づくことができた。しかし、この時はここから満点解法まで思い浮かばなかった。実力がない証拠だ。
いったんこの問題から離れて他の問題を考えて戻ってきたときに、いい感じに式で向きの変更がO(log N)回であることを示せて急いで実装した。少し雑な実装をしたのでバグってるかと思ったが、サンプルも一発で通ったうえ提出も一発で通った。この四日間で一番脳内にヤバい成分が出た。この問題は正直反省点が多いが、通せた喜びは大きかった。

結果: 8-37-100 計145で順位は変わらず総合8位だった。
一個上のぷらと差を結構詰めたが追いつけなかったのは少し悔しい。
競技中にぷらがいきなりかなり大きくガッツポーズをしていたのが印象的だった。昼飯のときにチューター陣でも話題になったという話を聴いた。
昼飯中に(学校の)OBのyutaka1999様が来ていたので挨拶とがっちり握手をした。JMOの春合宿にいるという話を風の噂で聞いていたのでびっくりした。

春季トレーニングを振り返って

結論、俺は弱かった。
悔しい気持ちももちろんあるがこの結果に納得してしまっている自分がいるのがとても悲しい。単純に上位勢にわからせられただけだった。
主な反省点としては単純に知識が足りていなかったこと、5時間集中することに慣れていなかったこと、Communicationを真面目に解いたことがなかったことなどがあったと自分では思っている。
とはいえJOI春は初参加で、コンテスト中にACを取れたことや他の参加者/チューターの方々たちとの会話で色々な学びを得られたことなどの貴重な経験ができた。ありがとうございます。
来年は代表になれるようこれから一年間しっかり自分磨きをしていきたい。

JOI2022/2023本選参加記

2023/2/5,12に開催されたJOI 2022/2023本選に参加しました。その参加記です。

2/11以前

今年は2/11,12が土日となっており、おそらく2/11が数学オリンピック本選の日なので配慮されて2/5に開会式/交流会となった。自分は様々な要因によりJOI初参加だったため正直よくわからない。

毎年恒例(自分は初参加だけど)の自己紹介動画は同学年の競プロerで新年会という名目でカラオケ/謎解きをした時に撮影した。自己紹介動画の内容はロシアンタコ焼き(一つだけ激辛タコ焼きがある)にした。カラオケで偶然そのメニューを見つけ、しかもその個数が人数と一致していたのでこれをやろうとみんなで話し合った。(もともと自己紹介動画みんなで撮影しよう~みたいな話にはなっていたがこんなにドンピシャでいいネタがあるとは思わなかった)
かなり楽しかったし面白かった

実際の競技については春合宿にとりあえず進出することを目標にしていた。難易度9以下を確実に取れれば本選突破は確実だろうと思ったので難易度8,9をちゃんと取れるように8,9を中心に埋めていった。10とか11とか12にも多少手を出したりした。IOIシラバスをちゃんと一通り読んだりした(してる人ほぼいなそうだけどしておくとよいと思います)。あとは難易度8,9レベルの問題を探しにoj.uzで海外OIの問題を漁ったりした。

本選までのJOI精進状況

2/4にtatyamさん主催によるJOI本選バチャがあった。そこまで多くの人は出ていなかったけどそれなりに満足のいく結果が出せたので少し安心した。

2/5 開会式/交流会

自己紹介動画は時間制限によりいきなりしおむすびがタコ焼きを食べ始めるという謎動画を提出したため、タコ焼きのツイートをしてそれをJOI鯖に貼って人々に理解してもらおうとした。
結果的に結構盛り上がり、辛いふりをした人などがいたため誰が当たったのかコメントしてくれてる人とかいて嬉しかった。
個人的にはCyanmond(cv.ずんだもん)による「全完宣言なのだ」が一番好きだった。

ツイート
結果発表

E8さんらによるチューター企画/交流会が夜にあって、とりあえず参加することにした。4人くらいのチームに参加者が振り分けられて問題を解くようなものだった。問題はヒューリスティックな感じの問題だった。色々うまくいって前半後半でともに一位を取って総合一位だった。チームメイトに感謝。
開会式から一週間はとにかく問題を解いたりした。それ以外逆に何をやればいいかわからない。

2/11

運よくJMO本選に進出したので午前は数オリの勉強をして13:00からの本選に向けて調整していた。数オリの方は(2/13現在では)結果が出ていないのでちょっと待ち遠しい。
数オリ後は同じ学校の数研の人々15人くらいでサイゼリヤに押しかけて夜ご飯を食べた。結構遅くまで居座ってしまい21:00からのABCに間に合うか微妙な時間で解散して急いで帰宅した。
結果的にABCに間に合い7完の58位と調子がそれなりに良かった。Gで直前の学校の受験休み中に勉強したCHTが出たにもかかわらずMonotone Minimaで解いて、ちょっと怖かったけど。
ABC後は数オリから切り替えようと思ってたところを、一緒に通話する人が見つかったのでその人ともう一人といざという時に使えそうなNyaan's Libraryの高度データ構造/アルゴリズムの使い方を習得した。最終的には使わなかったけど...

2/12

前日にABC後の通話で寝たのが2:00だったがなぜか8:30頃に起きた。(少し眠かった)
去年までは9:00開始だったが去年寝坊しそうだった人が発生したことからか13:00からと優しい時間になった。
起きてすぐはあんま実感はわかなかったが本選当日であることを自覚しはじめて緊張してきたのでアニメを見たりして時間を浪費した。
12:00ごろになってさすがに調整するかと思い、去年の本選のA,Bを解きなおすことにした。解いたことあるとはいえ10分程度で解けていったん安心できた。
昼飯はカップ焼きそばを食べて、気づいたら12:45になっていた。チョコやお水などを用意して自分の周りの環境を整えて準備をして、競技に臨んだ。

競技パート

提出等からムーブをおおまかに
方針としては基本的に順当に前から見て満点取れるうちは全部取って取れなくなったら全部の問題を読んで部分点に行くという方針で動いた。
13:00 競技開始。直前にネットが少し不安定になったので心配だったが(競技中はずっと)問題なかった。
13:07 1問目を読むと、同じ色の区間を管理して問題文の通りにやると満点が簡単に取れると分かり実装してAC(合計:100点)
13:16 2問目を読むと、絶対値が45度回転しろと言ってくるのでその通りにやるといい条件になる。直近一週間で平面走査をする問題を複数解いていたおかげか何も考えずに実装する。なぜかバグが発生せず一発でAC(合計:200点)
13:20 3問目を読むと、苦手意識のあるグリッド問題なのでいったん問題文を読んで解法が思い浮かばないか考えるが普通に見当がつかなかったので他の問題も読む。
13:30 4,5問目の問題を理解したがとりあえず3問目で部分点取ろうと思い、自明な小課題1を取る(合計:208点)
13:54 少し考えるとO(RCN2)が生えて小課題2が解けたので頑張って実装する。とりあえず提出すると0点でちょっと慌てる。
14:05 ほどなくして少しだけ嘘を書いていたことが発覚して直して提出するとACが出て安心する(合計:227点)
14:07 なぜか通らない想定の小課題4が通るかも~と思い、最初に小課題2以外をはじくために入れていたif文を直して小課題4のテストケースも走らせるとなぜか通る(合計:246点)
14:14 その後小課題5も通るんじゃないかと思い何回か定数倍高速化したりして提出してみるもだめだった。たかが5点の小課題5に粘着するのは良くないと思い3問目をいったん放置することにした。
14:24 問題を読んだ時点で小課題1,2,3の解法は分かっていたので一気に実装してうまく実装出来たので提出すると小課題3でTLEが発生してびっくりしたが小課題1,2は通っていた(合計:260点)
14:28 少し頭を悩ませたがコードをにらむとデバッグ出力をしたままにしていたことに気づき、消して提出すると小課題3も通る(合計:267点)
14:30 3問目で区間に辺を貼るテクを思い出して3問目が解けるのではと思い3問目に移動
14:41 実装出来たので提出してみると点数があがらずショックを受ける。おそらくO(RCN2)がうまくいきすぎたのだろう。
14:50 すこし落胆していたが4問目の小課題5が思いつき、20点だったためとてもうれしい気持ちになる。
15:04 実装が終わり提出するがなぜかWAやREなどが出ており焦る。
15:06 しかし、実際は定数倍を早くした方がいいかなと使い慣れていないstd::setemplace_hintを使ったのが見事に未定義動作を踏んでいた(と思われた)のをすぐ察したためその部分を修正して提出すると小課題5が通る(合計:287点)
15:10 ここで脳が疲れていることに気づき、用意していたチョコと水を飲んでいったん整う。
15:15 ほかの問題で進む未来が見えなかったため5問目の考察をすることにする。
15:31 実験の結果RとBの個数に関するいい考察が生えて小課題1,2,3が通ることを確信して提出すると0点が返ってきて焦る
15:32 ちょっと見ると変数が逆になっているところがありなおして提出すると小課題1,2が通った。なぜ??になる(合計:302点)
15:34 このあたりで通過はできるだろうという気持ちが少し沸いてきたが小課題3をどうしても通したいと思ったのでその気持ちを振り払ってコードとにらめっこしたりちょっとコードを直して提出するが今度は0点....
15:40 問題文をちゃんと読もうという教訓を思い出し問題文をもう一度読み直すとあれ?クエリごとにリセットされますか?となる。実際そうすると小課題3が通るカスがよ(合計:312点)
15:50 4問目に戻ったらO(N2)の解法が生えたので実装する。そうするとぬるっと通って小課題4をAC(合計:322点)
16:00 4問目でO(N)っぽい解法が生えるが、頭が疲れ始めてきてチョコも食べきってしまい頭をリフレッシュできず、頭が少し混乱して正しかったのに嘘と判断して4問目を捨てる(バカ)
16:05 5問目の小課題4の解法が生えたと思い実装をはじめる(実際は嘘解法だった)
16:17 嘘であることに気が付き絶望。
16:27 3問目で定数倍高速化で安心のためにもう5点通したいと思い定数倍高速化を試行錯誤する
16:45 3問目に試行錯誤している間に5問目の小課題4の正しい解法が生えるが少し実装がある上、残り15分だったため急いで書き始める。
17:00 結果的に間に合わず17:00になった瞬間台パンした。
JOI鯖でなにか連絡があるまで何もしてはいけないと思い、ぼーっとしていたら終了の連絡が来て、Twitterに復活したり同学年のerの鯖でお互い振り返りなどをしはじめた。
自分は正直やらかしたなと思っていたなか周りもそれなりにやらかしたと発言しており少し安心する。
なんやかんやで春は安泰そうだとJOI鯖を見てもわかったので安堵した。

チューターによる解説を聴きながら3問目絶対解けないなーと思ったり、4問目正しかったやん!カス!と思ったり感情がめちゃくちゃになって疲労を一気に感じ始めた。
5問目の解説で途中で理解できなくなり悲しかったがかなり天才だなーと感じた。
解説の後Gartic Phoneが開催されることになりなんとなくで画面共有しながらやった。絵心が無いことを自ら晒してしまい、恥ずかしかった。
Gartic Phone中から頭が痛くなりはじめたのでAGCは参加せず寝ることにして結局寝た。

2/13

2/12に2/13の午前中に成績優秀者の発表がありますと言われていたので朝から少しそわそわしていた。
通過しているだろうとは思っていたがなんやかんやでやらかしていたらどうしようなど様々な憶測が自分の中を飛び交って緊張していた。
学校の授業もほぼ聞かずにウェブサイトを無限にリロードしていた。
11:00頃に発表があり、Aランクであることを確認して休み時間中だったのでうれしさのあまり叫んでしまった。
同級生のボーダー付近の友人に結果が出ていることを知らせ、彼が通っていることを確認して二人で喜び合った。

総評

もちろん春合宿に通過できたことはうれしいが、競技については4を通せたり5の部分点を取ったりできるべきだったところがあるので悔しい気持ちでいっぱい。
悔しさをぶつけて春合宿では一桁順位を目標に一か月間頑張ろうと思う。

【でぶ Advent Calendar 2022 Day23】でぶになります

この記事はでぶAdvent Calendar 2022 23日目の記事です。
(ほぼ)ネタ記事です。

経緯

・親に久しぶりに会ったら痩せすぎと言われたのでもうちょっと太ろうと思った。 ・shojin_debuさんはどのくらいこの世界を占めているのかを自分の身で実感したかった。

やったこと

期末考査期間中、毎日不健康そうなものを食べる。不健康なものをたくさん食べればきっとでぶになれるだろう!

Day1

試験科目: 漢文 数学A 家庭科
食べたもの:マクドナルドの15ピースチキンナゲット,ポテトM,スプライトM
感想:イギリスは20ピースだったので少し物足りなかった。これでは太らないのでは。

Day2

試験科目: 地学 英語1 古文
食べたもの: 吉野家のチーズ牛丼
感想: ひよって並を頼んでしまい、お腹は満たされなかった...
(写真忘れた)

Day3

試験科目: 物理 数学B
食べたもの: らーめん いっとくのつけめん
感想: 麺が好みの中太でおいしかった。おすすめです。
(写真忘れた2)
宣伝

Day4

試験科目: 公民 技術
食べたもの: 馬場壱家 風の陣のラーメン
感想: スープ濃いめに初挑戦したところ結構重かった。相変わらずばばいちのスープはうまい。かなりお腹が満たされた。
宣伝

Day5

試験科目: 英語2 国語
食べたもの: 焼肉ライク ミックスカルビセット
感想: それなりの量の肉に白米おかわり自由で1050円というコスパの良さに感動した。お米がかなり進んでお腹がかなりいっぱいになるくらい食べた。

宣伝

結果発表~~~~!!

予想通りではあったが5日間では別に太らなかった...
試験期間にもかかわらずワールドカップを見まくっていて生活習慣が破滅し、同時にこの5日間で(どちらかといえば)不健康なものを食べたせいでかなり大きな口内炎ができた。
最悪です。今もまだ残っててつらい....

おまけ

競プロにおいて、メモリ制限は軽視されがちな制約です。(実際ないようなものである場合が多い)
しかし、現実世界では痩せられなくとも競プロでメモリ節約できればダイエット成功とみなすことができるので、今回はそんなメモリ節約に関するアルゴリズムを一つ紹介してみなさんのダイエットに協力します!
そのアルゴリズムというのはフロイドの循環検出法です。別名 The Tortoise and Hare Algorithm。
単方向連結リストのような構造やFunctional Graphの循環を検出するアルゴリズムです。
競プロでも有名なワーシャルフロイド法と同じ人が発案したアルゴリズムなので同じ名前がついていますがアルゴリズム自体は全く別物です。

アルゴリズム

今回は競プロでよく出るFunctional Graphであると仮定します。また、Nを頂点数であるとします。
関数 fを行く先の頂点を返す関数とします。
また、 a_0を任意の頂点にして a_{i+1}=f(a_i) として、ある頂点の列を考えます。
今回やりたいループ検出の愚直な方法として、この列の要素を a_0から順にいちいち記録していって、同じ要素が出るまで探すというものがあります。これは空間 \mathcal{O}(N)かかります。
しかし、今回のアルゴリズムではなんと空間 \mathcal{O}(1)しかかかりません!
注目するのは a_i=a_jが成り立つとき |i-j|はループの長さの整数倍となることです。①(証明略)
フロイドの循環検出法では2つのインデックスをもって一方のインデックスを1ずつもう一方を2ずつ増やしてゆき、一致する場合を探します。
インデックスを増やしていくのを繰り返すとある時点で a_m = a_{2m}となり、①より 2m-m=mのある約数が循環の長さになります。
この一致が見つかったあと、ループの長さは愚直な方法と似た感じで mから1ずつ増やしていって a_mと一致したときのインデックスと mの差がループの長さとなり容易に特定できます。
このアルゴリズムは実際に空間 \mathcal{O}(1)であることがわかります。

競プロでの用途

このアルゴリズム自体の具体的な用途というものはあまり思い浮かばないですが、ポラード・ロー素因数分解法はこのアルゴリズムに基づいているので知っていたほうが良いと思い紹介(布教)しました。

追記: shojinさんはshojin_debuと呼ぶべきなのかshojin_proと呼ぶべきなのか最近悩んでいます。どちらが正しいんでしょうか....

SECCON Beginners CTF 2022 Crypto Writeup

SECCON Beginners CTF 2022に参加しました。CryptoはHardの一問以外解けたのでWriteupを雑に書こうかなと思ったので書いてる。

Coughing Fox

problem.pyが与えられて解読する。正直やるだけ。

from random import shuffle

flag = b"ctf4b{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"

cipher = []

for i in range(len(flag)):
    f = flag[i]
    c = (f + i)**2 + i
    cipher.append(c)

shuffle(cipher)
print("cipher =", cipher)

テキトーに一文字ごとに全探索すればいい。

import gmpy2
cipher = [12147, 20481, 7073, 10408, 26615, 19066, 19363, 10852, 11705, 17445, 3028, 10640, 10623, 13243, 5789, 17436, 12348, 10818, 15891, 2818, 13690, 11671, 6410, 16649, 15905, 22240, 7096, 9801, 6090, 9624, 16660, 18531, 22533, 24381, 14909, 17705, 16389, 21346, 19626, 29977, 23452, 14895, 17452, 17733, 22235, 24687, 15649, 21941, 11472]
flag = ""
for i in range(0, len(cipher)):
  for c in cipher:
    x=gmpy2.iroot(c-i, 2)
    if x[0]*x[0]==c-i:
      flag += chr(x[0]-i)
      break
print(flag)

できたー!

PrimeParty

server.pyが与えられて通信してflagあてる感じ。 デカい素数出力してやればいい。

from Crypto.Util.number import *
from secret import flag
from functools import reduce
from operator import mul

bits = 256
flag = bytes_to_long(flag.encode())
assert flag.bit_length() == 455

GUESTS = []

def invite(p):
    global GUESTS
    if isPrime(p):
        print("[*] We have been waiting for you!!! This way, please.")
        GUESTS.append(p)
    else:
        print("[*] I'm sorry... If you are not a Prime Number, you will not be allowed to join the party.")
    print("-*-*-*-*-*-*-*-*-*-*-*-*-")

invite(getPrime(bits))
invite(getPrime(bits))
invite(getPrime(bits))
invite(getPrime(bits))

for i in range(3):
    print("[*] Do you want to invite more guests?")
    num = int(input(" > "))
    invite(num)

n = reduce(mul, GUESTS)
e = 65537
cipher = pow(flag, e, n)

print("n =", n)
print("e =", e)
print("cipher =", cipher)

十分に大きい素数をゲットする。

from Crypto.Util.number import getPrime
print(getPrime(512))

あとは通信してe,cipherをゲットして普通に解読すればいい

from Crypto.Util.number import *
p=
e=65537
d=inverse(e, p-1)
cipher=
print(long_to_bytes(pow(cipher,d,p)))

おっけ~~~

Command

``chall.py''が与えられる。見るとAES暗号っぽいけどivを得られるから関係ないっぽい

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import isPrime
from secret import FLAG, key
import os

def main():
    while True:
        print('----- Menu -----')
        print('1. Encrypt command')
        print('2. Execute encrypted command')
        print('3. Exit')
        select = int(input('> '))

        if select == 1:
            encrypt()
        elif select == 2:
            execute()
        elif select == 3:
            break
        else:
            pass

        print()

def encrypt():
    print('Available commands: fizzbuzz, primes, getflag')
    cmd = input('> ').encode()

    if cmd not in [b'fizzbuzz', b'primes', b'getflag']:
        print('unknown command')
        return

    if b'getflag' in cmd:
        print('this command is for admin')
        return

    iv = os.urandom(16)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    enc = cipher.encrypt(pad(cmd, 16))
    print(f'Encrypted command: {(iv+enc).hex()}')

def execute():
    inp = bytes.fromhex(input('Encrypted command> '))
    iv, enc = inp[:16], inp[16:]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    try:
        cmd = unpad(cipher.decrypt(enc), 16)
        if cmd == b'fizzbuzz':
            fizzbuzz()
        elif cmd == b'primes':
            primes()
        elif cmd == b'getflag':
            getflag()
    except ValueError:
        pass

def fizzbuzz():
    for i in range(1, 101):
        if i % 15 == 0:
            print('FizzBuzz')
        elif i % 3 == 0:
            print('Fizz')
        elif i % 5 == 0:
            print('Buzz')
        else:
            print(i)

def primes():
    for i in range(1, 101):
        if isPrime(i):
            print(i)

def getflag():
    print(FLAG)

if __name__ == '__main__':
    main()

fizzbuzzでもprimesでもいいけどうまくxorとればいい。 コードなくした。パディングに気を付ける。

Unpredictable Pad

chall.pyが与えられる。乱数わかればいいのがわかる。

import random
import os

FLAG = os.getenv('FLAG', 'notflag{this_is_sample_flag}')

def main():
    r = random.Random()

    for i in range(3):
        try:
            inp = int(input('Input to oracle: '))
            if inp > 2**64:
                print('input is too big')
                return

            oracle = r.getrandbits(inp.bit_length()) ^ inp
            print(f'The oracle is: {oracle}')
        except ValueError:
            continue

    intflag = int(FLAG.encode().hex(), 16)
    encrypted_flag = intflag ^ r.getrandbits(intflag.bit_length())
    print(f'Encrypted flag: {encrypted_flag}')

if __name__ == '__main__':
    main()

624個の32bit整数分の乱数吐かせると乱数を特定で切るっぽいので、1個目は62232bit、2,3個目は32bitの乱数を出力させたい。 62232bitは-(2622*32-1)を出力すればif文のところも突破できる!!あとはやるだけ~ (参考:Mersenne Twisterの出力を推測してみる - ももいろテクノロジー)

import random
from Crypto.Util.number import *

def untemper(x):
    x = unBitshiftRightXor(x, 18)
    x = unBitshiftLeftXor(x, 15, 0xefc60000)
    x = unBitshiftLeftXor(x, 7, 0x9d2c5680)
    x = unBitshiftRightXor(x, 11)
    return x

def unBitshiftRightXor(x, shift):
    i = 1
    y = x
    while i * shift < 32:
        z = y >> shift
        y = x ^ z
        i += 1
    return y

def unBitshiftLeftXor(x, shift, mask):
    i = 1
    y = x
    while i * shift < 32:
        z = y << shift
        y = x ^ (z & mask)
        i += 1
    return y


payload1 = -(2**(32*622-1))
res1 =??
res1^=payload1
payload2 = 2**31
res2 =??
res2^=payload2
res3 = ??
res3^=payload2
value1=[]
for i in range(622):
  value1.append(res1&(2**32-1))
  res1>>=32
value1.append(res2)
value1.append(res3)
mt_state = tuple([untemper(x) for x in value1] + [624])
random.setstate((3, mt_state, None))

predicted =random.getrandbits(239)
print(predicted)
encrypted_flag = ??
flag=encrypted_flag^predicted
print(flag.bit_length())
print(flag)
print(long_to_bytes(flag))

これでできる~~

最後の問題242から探索範囲絞れるのか??になって終了 次のSECCON Beginners CTFのCrypto全完してえ~~~