AtCoder青になったので水色から青までの一年半とついでに今まで書きそびれた黒から水までも全部書く
自己紹介
DivineJKです。地元の友達からは苗字で呼ばれています。
10月2日に、AtCoder青になりました。数学が強いタイプです。
音楽は主に洋楽メタルが好きです。最近はK-POPも聴き始めました。
あとふわふわツインテールとか好きです。なんというか、ツインテールに限らずハーフアップとかポニーテールとかとりあえずなんでも好きです。嘘かもしれません。
ゆめロリとか甘ロリとかも好きです。というか、原宿系の趣味があります。その影響でメイクを勉強中です。
あと、たまにイラストを描いたりしてます。最近は女子の制服を描くのとかマイブームです。正直イラストの方の精進もしたいんですよね。
このご時世で最近はなかなかできないのですが、地下アイドルのヲタをやったりもしてます。推しのアイドルは、Twitter(@realDivineJK)できいてくれたら教えます。
趣味の話はさておき、軽く自分の経歴を紹介します。
大学では物理学を専攻し、もう一回遊んでから卒業しました。ママ、パパ、ごめんな。まじで朝起きれないし電車乗ると吐き気する。
で、そのもう一回遊ぶときに、単位数確保のためにフラッと別の学部のプログラミングの授業でPythonに触れたら、見事にハマってしまいました。ハマりすぎてBCH公式の各項の係数計算や、スピン合成の固有状態の計算なんかも自主的に組んでました。そんなわけでPythonで遊びながらTwitterサーフィンしていると、顔の前に手を置いて何かを考えているアイコンのとある社長のツイートで、競技プログラミングやAtCoderの存在を知りました。
僕は今まで数学オリンピックとか情報オリンピックとかに無縁の人生だったので、競技学問ってちょっとハードルが高いなーとか、凡人の自分には無関係かなーと思っていました。僕は科学オリンピックや開発や研究ですごい成果をあげたとか、中学高校がとんでもない進学校だったとかそういうわけでもなかったので。
しかし、実際にAtCoderの問題を開いてみると、意外に解ける!となって、それからどんどん問題を解く楽しみを味わっていきました。多分、僕の探していたものはこれだったんじゃないか、僕の適正はここなんじゃないかとか思えてきて、もう少し早く出会いたかったなって思いました。
とりあえず自己紹介はこれで以上です。他何か聞きたいことがあったら僕のTwitter(@realDivineJK)のリプとかDMにでも質問してきてください。答えたくない質問はスルーしますけど。
びふぉーあふたー
これが、
こう。
ジャスト+400!
およそ1年半かかりました。Ratedコンテスト初参加からなんと2年1ヶ月(!)
水になるまでのハイライト
1997/08/27 誕生
生まれました。
やったこと:
- 生まれる
2019/09/01 黒から灰になる(ABC139)
成長して、その過程で世の中の色々な理不尽や矛盾と闘いながら、そんな中でも家族や友達と笑い合える束の間の幸せを噛み締める日々を過ごしているときにAtCoderに出会い、黒から灰になりました。
やったこと:
- AtCoderを知ってコンテストに出る
2019/11/04 灰から茶になる(AGC040)
AGCに参加したら茶色になりました。この当時は灰色もAGC ratedだったのだ。
やったこと:
- コンテストに出る
2020/01/11 茶から緑になる(第6回ドワンゴからの挑戦状 予選)
スマホコーディングで青Perfとって緑!w 8級から7級すっ飛ばして6級!w
バイト帰りにスマホから参加。A問題でRE出たときは初めての冷えを覚悟しました。スマホの充電残り7%でB問題が通ったときは街中でスキップしてました。AtCoderはじめた当初は緑ぐらいを目指して終わろうかなと思ったのですが、競プロの知識は結構面白かったので続けることにしました。
やったこと:
- mod逆元
2020/03/28 緑から水になる(ABC160)
3ヶ月で緑から水になりました。あとワニが死にました。
2020年になってから人が変わったように水Perfがとれるようになりました。ちなみに初水Perfより初青Perfのほうが先という謎の現象が起こっています。
やったこと:
水から青のふりかえり
ABC161〜ABC168(2020/04/04〜2020/05/17)
Rating 1203 -> 1311
水色になってもだいたい緑のときと同じようなPerf(1300くらい)をとっていて、これ1300になったらどうしようとか思いながらコンテストに参加していたら、いつのまにか1300台にのってました。
なんというか、このときは自分が青になる世界が想像できてなかったです。きっとレートは上がるけど青にはなれないだろうな、みたいな。でも青にはなりたかったです。
AGC044〜ABC171(2020/05/23〜2020/06/21)
Rating 1311 -> 1245
AGC044で0完太陽をとってしまい、およそ半年ぶりの茶Perfで激冷えしました。それ以来、NoSub戦略というものを選択肢にいれつつコンテストに参加していました。
また、ABC169で全完のチャンスだったのですが、自分の実装力の無さというか、実力不足で結局冷えになってしまったのが悔しかったです。
ABC172〜AGC048(2020/06/27〜2020/10/18)
Rating 1245 -> 1349
ABC172で初めてtourist出しというものをやってみました。これが大成功(Perf 1988)して以来、先のNoSub戦略と合わせて、企業コン以外のABCは「Eが解けたら出す、解けなかったら出さない」と決めてコンテストに参加するようになりました。
ARC106〜ABC186(2020/10/24〜2020/12/19)
Rating 1349 -> 1494
ARCで2回黄Perfとって一気にHighest1500台になりました(は?)。ただ、ABC181で緑diffのEが通せなくて、ここで自分の実装力の無さやあいまいさと直面することになりました。
ABC186以降はNoSubを繰り返すなどして空白の3ヶ月になりました。
ARC112〜ABC199(2021/02/13〜2021/04/24)
Rating 1494 -> 1477
復活します。もうこの時期からNoSub戦略に飽き出したというか、レートを落とす覚悟で行くか、と考え出しました。
ここから3回青以上のPerfが取れて、Highestが1576になったときは青見えた!やばい!青見えた!と思いました。
しかし、ABC198とABC199で激冷えして戦意喪失しました。このときは、もう自分は競技者としての適性がないんじゃないかとも思えて、引退も考えました。また3ヶ月近くコンテスト不参加の日々が続きました。
ちなみに、3月に大学を卒業して、4月頭からプログラミング系のアルバイトを始めました。
ABC212〜ABC221(2021/07/31〜2021/10/02)
Rating 1477 -> 1603
やっぱり問題が解けない、解けない問題が多いのは嫌だったので、人々の影響もあってABCバチャをやることにしました。
8問ABCになるので出てみるか、という軽い気持ちで出たらABCDGの5完で激温まりました。嬉しかったので、またコンテスト参加をはじめることにしました。
レートが1579になったとき、体調不良で参加できなかったABC219のバチャに参加したら、
本番なら黄Perfの成績でした。せつない。
このときから僕は「実質青」を名乗っていました。
そして、レート1563で迎えたABC221。
僕はこの回で青になれるとは思ってなくて、まああったまったらいいかな、青Perf取れたらいいな、ぐらいの気持ちでした。
そしたら、ABCDまで順調に進み、E問題の解法もすぐに浮かびました。BITに数を入れる処理でmodとり忘れてTLEでペナしたけど、ペナ含め47分でEが解けたのはわりと順調だなと思い、順位表を見たら...。
...ええ、250位とかになってました。
これ、このまま行けば黄Perf出るじゃないですか。AtCoder参加して初めてペナルティの5分に対する後悔を覚えました。
というか、ペナ消化した後の順位の下がり方がえらい鈍くて、え、これワンあるのでは?青コーダー誕生ちゃいますか?そう思い、ac-predictorを見に行ったら、1563->の矢印の先に青い数字が表示されていて、僕は落ち着きを失いました。
もうそのあとはPerfが1888以下にならないことを祈るのみ。でもやっぱり落ち着かないので、FとかHを解いて順位を解いて順位を少しでも上げようと頑張りました。結局解けずに終わったんですが。
コンテスト終了後も順位表は見れませんでした。Perfが1888になる順位が大体わかっていたので。必死にAnthrax聴いて気を紛らわせていました。
で、Twitterの人々がコンテスト結果をツイートした瞬間に自分のプロフィール画面を観にいきました。自分の名前が濃い青になってて思わず手を叩いてコロンビアになってました。
レートが1579で元Highestのときですら青になれる実感が湧かなかったので、まさか青が実現できるなんて驚きと嬉しさが隠し切れないですね。
あと心の余裕がなかったので画像が準備できなくてすみません。
水から青になるまでにやったこと
過去問解くやつ
AGC044からABC199までは停滞期と呼んでいいかもしれません。そして停滞期の時期は、コンテストをレート上げのためのものだと思っていたかもしれません。レートが上がれば嬉しいし、レートが下がったらカス以外の感想がなかったです。
しかしあるとき、「僕のヒーローアカデミア」1巻を読みました。デクくんが雄英合格に向けて積み上げを繰り返してるシーンは親指を立てながら涙なしにはみられませんでした。
そして、このシーンで僕はあることを思いました。
「レートは一時的に上下するかもしれないけど、解いた問題数は下がることはない。そして、解いた問題数が多いほどレートが上がる確率が高くなるから、過去問を積み上げない手はない!」
というわけで、一旦レートはおいといて、解いた問題数を積み上げていくことにしました。
特に、ABCではバンバン解説を開けて知らない知識を勉強していこうという気持ちで臨みました。僕は今まで特別な天才エピソードもなく、本当にただの凡人なので、解法が思いつくのを待つよりは解説を読んで理解するほうが効率がいいかなーとも思ったのです。
なんというか、パズルを解く楽しさより知識を得る喜びを求めたというか。知らない知識に触れたり知識を積み上げていく日々は刺激に溢れて充実してました。
そうして知識積み上げの日々を過ごすと、レートが落ちてもまた上げればいいしな、みたいに思えて冷えに対する抵抗が減ったと思います。
そういう充実した日々を過ごすと、自然と人は笑顔が増えていくものです。
笑顔で明るく、穏やかな気持ちで過ごしている人とは関わりたいじゃないですか。そんなわけで、ゆったりとしたそんな気持ちで過ごしていたらおよそ3ヶ月で性別、国籍問わず友達がめっちゃ増えました。
緊急事態宣言が出てたのでリモートだったのですが、飲み会なんかもやったりしました。
そして、その中の一人の女性とお付き合いすることになりました。
出身は宮城で、去年東京にきたんだそうです。まだ東京慣れしてなくて、休みの日は原宿デートがメインですが東京探訪をしています。
彼女はとても感情豊かで、話しているととっても癒されます。この間も、コンテストで冷えたことを報告したら泣きながら僕の胸元に抱きついて慰めてくれました。
仕方ないので、僕が頭を撫でていると、気づいたら僕の胸元で寝ていました。まったく、どちらが慰めてるのか、って感じですね。
そんな感じで、青になった今も楽しく問題を解いています。今では彼女と結婚に向けた話を進めているところです。
データ構造・アルゴリズム・知識
水の時期にやったと思われる内容をほぼ全て紹介します。
- Segment Tree
- Lazy Segment Tree
- Binary Indexed Tree
- Slope Trick
- Li Chao Tree
- 重み付きUnion-Find
- Euler Tour
- 木の半径
- 非再帰DFS
- 形式的冪級数
- グレイコード
- Suffix Array(SA-IS)
- Manacherのアルゴリズム
- Run-Length Encoding
- Z-algorithm
- 偏角ソート
- 円の共有点、包含関係
- 全方位木DP
- 凸包(O(N2))
- 2次元アフィン空間
- SCC
- LCA
- 木の重心
- ベルマンフォード法
- ダイクストラ法
- クラスカル法
- TSP
- bitDP
- 牛ゲー
- ワーシャルフロイド法
- NTT
- 中国剰余定理
- Garnerのアルゴリズム
- 高速ゼータ変換
- 高速メビウス変換
- 高速アダマール変換
- 離散対数
- レピュニット数
- 約数系包除
- FloorSum
- and畳み込み
- or畳み込み
- xor畳み込み
- スターリング数
- カタラン数
- 分割数
- 多点評価
- 多項式補間
- LIS
- 半開区間は便利
- 桁DPをやるときは母国語でコメントを書いた方がいい
- 中央値を求めたいときは、ある数以上のものを1、そうでないものを0として二分探索して調べる
- 二次元配列は一次元に直した方が気持ち軽くなることがある
- Union-Findではサイズが大きい集合に繋ぎ直すと計算量が落ちることがある
- インドゾウの足の裏の面積からインドゾウの体積を求める方法(Sreekumar Function)
- 日焼け止めは将来のためにどんな理由にせよ塗っておいた方がいい
- コンドームを付けて致すと手は洗ってもベタベタだけどゴミが少なくなるしにおいも汚れも少ない
- 半蔵門線は水天宮前駅で日比谷線の人形町駅と接続するというが、それは真っ赤な嘘
- アヴ様の誕生日は8/27ではなく9/27
- セグ木の二項演算は抽象度を上げるために外部から入れた方がいい
- 睡眠は大事
イベント
- 卒論執筆
- 大学卒業
- 大学院入試
- プログラミング系アルバイト
- 正社員登用
ちなみに、過去問のとこで述べた彼女の話は全て作り話です。
まあこんな下手くそな嘘で騙されるような純粋な方はさすがにこの世に存在しないとは思いますが、万が一騙されたという人がいれば、今度インチキ量子論セミナーに送り込んでやるので覚悟しておいてください。
ファッション
スカートパンツを買いました。結構良いんですけど、自転車に引っかかりそうで怖くて着れてないです。
あと化粧水と乳液でのケアを始めました。
アイドルヲタ
推しが卒業とグループ解散を経験し、現在活動が第3章です。
イラスト
カイリューさんにファンアートをプレゼントしたり、きりみんちゃんのイラストを描きました。
蟹江もなみさんときりみんちゃんの4コママンガも描きました
これからやる予定のこと
これからの目標というか、これできたら強そうとか強くなれそうだなとかさまざま。
- AtCoder Rating1800 → 達成できたらARC出る
- 任意modFPS
- フロー
- 2-SAT
- Segtree beats
- Codeforces復帰、Rating1900
- MojaCoder作問
- 問題解説記事執筆
さいごに
青になるのは難しいですが、青diff以上の問題は面白いです。解説ACでもやってみる価値ありです。
ごめん言い忘れた
少なくとも水色安定したいのであれば、BITとセグ木のライブラリは忘れずに整備しておきましょう。
それでは、今日はこの辺で。
黄色になったときの色変記事に続きます。
ABC180F - Unbranchedを形式的冪級数を使って解く
形式的冪級数(FPS)でやる解法で解いたので、まとめてみます。
問題リンク↓
考察
グラフに課される条件のうち、最初の2つの条件
- 自己ループを持たない
- すべての頂点の次数が以下である
から、考えるべきグラフは直線グラフか頂点数2以上のサイクル(を集めたもの)です。そして、3つめの条件
- 各連結成分のサイズを並べたとき、その最大値がちょうどである
についてですが、典型知識として、最大値がちょうどのものの数え上げは、(最大値が以下であるものの個数)-(最大値が以下であるものの個数)を求めれば良いです。ここまでは公式解説の通りです。
条件を満たすグラフで、連結成分のサイズの最大値が以下であるものについて考えます。まずは、各連結成分のサイズの振り分け方を決めます。サイズの直線グラフの個数を、サイズのサイクルの個数をとおくと、頂点数と辺の数の条件から、
という関係があります。この2式を整理して、
としておきます。
次に、各連結成分の頂点に番号を振ることを考えましょう。サイズの直線グラフの各頂点に与える番号を個とってきます。これらを各頂点に並べる方法は、反転して同じになるものを考慮してのとき通り、のとき通り、のとき通りです。これを一般にとおきます。
サイクルについても同様に、のとき通り、のとき通り、のとき通りです。これを一般にとおきます。
さらに、辺にラベルがついてないことから、同じ頂点数、同じタイプの連結成分は区別しません。これをふまえて、求める個数は
です。和の範囲はが先ほどの条件を満たすようにとります。
形式的冪級数を使う
さて、この和の中にある積の部分ですが、なにか見覚えがないでしょうか?そうですね、expの級数展開の項に出てくるやつですね。以下のものを考えます。
これのの指数は、それぞれ条件よりとなります。そしてこれは、
のの項にあたります。それぞれの級数はまさにexpの形そのものなので、それを用いて整理すると、
というとてもすっきりした形になります。これのの項を求めたいのですが、変数が2つあると複雑なので、偏微分的な要領でを定数とみなしての項を求めましょう。exp型なので、この場合はで回偏微分すれば良いです。結局、
のの項を求めればよいことになります。これをでも同様に行い、その結果をのときの結果から引けば答えが求められます。
ですが、が小さいことと、形式的冪級数の逆数などが登場しないことから、FPSのライブラリを持ち出さずにexpの冪級数展開を用いて計算したりしても間に合います。
ABC193E - Oversleepingの解説を書いてみます
コンテスト中に通せたので、自分の解法を書いてみます。
問題
概要
電車が秒で一方の街からもう一方の街に移動し、街に着いたときに秒停車する。高橋君は秒寝て秒起きるを繰り返す。高橋君は街で降車することができるか?もし可能ならば、最短何秒で降車できるか?ケースについて求めよ。
制約
厳密な問題の定義はもとの問題文を読んでください。
解法
状況整理
電車が街を出発してから、戻ってきて次に街を出発するまで秒かかります。つまり、電車の運動は周期秒の周期的な運動です。同様に、高橋君の睡眠に関する行動の周期は秒です。ところで、高橋君が街で電車を降りるためには、
- 電車が街に停車している間に高橋君が目を覚ます。
- 高橋君が起きている間に電車が街に到着する。
のいずれかが成立していれば良いです。そして、このいずれかが成り立つ最小の時刻が求める答えとなります。
周期的な運動であることから、電車の運動の状態は、時刻をで割った余りによって決まります。つまり、時刻をとすると、で表したときのによって電車の状態が決まります。一方、高橋君についても同じようにのによってのみ決まります。を消去して、の形にすることで、についての二元一次方程式が出来上がります。このそれぞれの時刻の表示を用いて、高橋君が街で電車を降りる条件を定式化してみると、
- かつ、つまり、方程式
- かつ、つまり、方程式
となります。それぞれの条件において、固定されていない方のおよびのとりうる範囲は高々なので、全ての場合について方程式を解いてみれば良いです。
商とあまりの定義
整数について、整数を用いて、と表します。をで割ったあまりとは、をこのような形で表したときのであって、となるような唯一の整数のことをいうものとします。また、をで割ったときの商とは、にをで割ったあまりをとったときののことを指すものとします。
以下に例を示します。
あまり | 商 | ||
---|---|---|---|
24 | 5 | 4 | 4 |
-24 | 5 | 1 | -5 |
24 | -5 | 4 | -4 |
-24 | -5 | 1 | 5 |
40 | -8 | 0 | -5 |
-13 | 26 | 13 | -1 |
-17 | 17 | 0 | -1 |
-11 | -5 | 4 | 3 |
拡張ユークリッドの互除法
いきなり一般的な問題を解くのは難しいと思うので、具体例を考えます。という方程式を解いてみましょう。後で示しますが、の係数(ここでは)の最大公約数が右辺の約数であるとき、またその時に限り、この形の方程式は整数解を持ちます。よって、この具体例では整数解が存在します。
2つの変数に対して方程式が1つなので、整数解は無数に存在します。たとえば、は解のひとつです。他にもも解のひとつです。このような解のひとつを機械的に求めるために、ユークリッドの互除法を使います。以下のように進んでいきます。
- あまりがになったので終了。
よって、最大公約数がであることが求まりますが、これらの各式をの形でみたときに、がどのように変化するかを行列表示で見てみたいと思います。
行列表示を知らない方もいると思うので必要なことだけ簡単に説明すると、行列とは要素を矩形に並べたもので、行列同士の積として
がなりたちます。また、2つの行列が等しいとは、両方の行列のサイズが等しく、かつ同じ場所にある要素が等しいことを意味します。
として、番目の式をと表します。ユークリッドの互除法のアルゴリズムのもっとも重要な事実として、との最大公約数はとと等しい、というものがあります。よって、です。このことを行列表示すると
となります。これは
のように書けるので、
とおきます。すると、
となります。この行列を求めることや計算していくことは、漸化式にしたがってその都度一つずつ順番に行えます。そして、初期値によらず最終的にはが、がの最大公約数になって終了するので、
となります。これの上側の要素を比較するとという式を得ます。このは元の方程式の解になっています。具体的には、
であるので、解が見つかりました。この解を利用することで、の解はこれを2倍したがみつかることもわかります。
また、をに変えるまでの最悪計算量は、がフィボナッチ数列の隣り合う項のときなのでです。
さらに、も方程式を満たすことがわかると思います。このことは一般に、に整数解が存在するなら、が以上未満になるように解を決めることが可能であることが言えます。そのように実装しておくと、解が存在しないときにを返すことで判定できるので便利です。
改めて、方程式の整数解を求める手順をまとめてみます。ここで、ををで割った商、ををで割ったあまりとします。
- とする。
- がならば手順4. へ。そうでなければ手順3. へ。
- とする。手順2. にもどる。
- この時点ではとの最大公約数になっている。もしがで割り切れなければ、を返し終了。
- とする。
- を倍する。
- も方程式の解としてとれる。とすると、となる。
- を返して終了。
本題
拡張ユークリッドの互除法で条件1. と条件2. を満たすを求めます。ただし、解が存在するならばどちらも以上でなくてはならないので、その条件も確かめる必要があります。は自動的にこの条件を満たします。が負になってしまう可能性があるように思えますが、それは起こらないことが示せます。
であったとすると、式変形により、条件1. では、条件2. ではですが、、であるので、自動的にが以上になることがわかります。
さらに、以上の方法で求めたは最小の非負整数解であるので、これで欲しいものは求まりました。あとは、解が存在するようなのもとでの最小値を求めれば良いです。
全体的な計算量はです。
コンテスト中のACコード
余談
解が存在しないときの出力が'infinity'
になっていることに気をつけましょう。僕はそれで2ペナ食らいました。
ABC183E - Queen on Grid:自分用メモ
問題
行列のマス目がある。左上のマス目をとして、上から番目、左から番目のマス目をマスとする。1手ごとに、そのマスから下、右、右下方向の好きなマスに移動できる。ただし、障害物に移ったり、障害物をを飛び越える移動や、マス目の外に出る移動は許されない。マスに移動する方法は何通りか?
初手の考察
素朴な遷移式
マス目にいく方法をとする。マス目について、左、上、左上方向のそれぞれで最もに近い障害物のある座標をとする。遷移式は
となる。
実装
メモ化再帰で実装します(思考停止)。
結果
落ちます。
考察をやり直す
という値を定義します。こうすると、遷移式は、
です。それぞれの値について、特徴を整理してみます。
トポロジカル順序を考えると、どの式もについて、の昇順でを更新していく方針で問題なさそうです。
正しい実装
群論と離散対数のお時間です - 3
さて、ここからが本題です。で、とが互いに素とは限らない一般の場合の離散対数問題を解きます。
実験
からの向きに辺を結んでできた有向グラフを考えます。鳩の巣原理より、どのようなについても、少なくとも乗するとより小さい指数乗の値に戻ることがわかるので、いつかは同じ頂点に戻ってきます。この巡回する部分を「サイクル」と呼ぶことにします。また、サイクルに含まれない部分を「テール」と呼ぶことにします。いくつかのについて、がどのようになるか見てみたいと思います。
mod 10
の素因数分解 | の約数 | |
---|---|---|
サイクルの長さ | テールの長さ | |
---|---|---|
0 | 1 | 1 |
1 | 1 | 0 |
2 | 4 | 1 |
3 | 4 | 0 |
4 | 2 | 1 |
5 | 1 | 1 |
6 | 1 | 1 |
7 | 4 | 0 |
8 | 4 | 1 |
9 | 2 | 0 |
mod 8
の素因数分解 | の約数 | |
---|---|---|
サイクルの長さ | テールの長さ | |
---|---|---|
0 | 1 | 1 |
1 | 1 | 0 |
2 | 1 | 3 |
3 | 2 | 0 |
4 | 1 | 2 |
5 | 2 | 0 |
6 | 1 | 3 |
7 | 2 | 0 |
mod 30
の素因数分解 | の約数 | |
---|---|---|
サイクルの長さ | テールの長さ | |
---|---|---|
0 | 1 | 1 |
1 | 1 | 0 |
2 | 4 | 1 |
3 | 4 | 1 |
4 | 2 | 1 |
5 | 2 | 1 |
6 | 1 | 1 |
7 | 4 | 0 |
8 | 4 | 1 |
9 | 2 | 0 |
10 | 1 | 1 |
11 | 2 | 0 |
12 | 4 | 1 |
13 | 4 | 0 |
14 | 2 | 1 |
15 | 1 | 1 |
16 | 1 | 1 |
17 | 4 | 0 |
18 | 4 | 1 |
19 | 2 | 0 |
20 | 1 | 1 |
21 | 1 | 1 |
22 | 4 | 1 |
23 | 4 | 0 |
24 | 2 | 1 |
25 | 1 | 1 |
26 | 2 | 1 |
27 | 4 | 1 |
28 | 4 | 1 |
29 | 2 | 0 |
mod 12
の素因数分解 | の約数 | |
---|---|---|
サイクルの長さ | テールの長さ | |
---|---|---|
0 | 1 | 1 |
1 | 1 | 0 |
2 | 2 | 2 |
3 | 2 | 1 |
4 | 1 | 1 |
5 | 2 | 0 |
6 | 1 | 2 |
7 | 2 | 0 |
8 | 1 | 1 |
9 | 1 | 1 |
10 | 1 | 2 |
11 | 2 | 0 |
予想
以上の結果から、いくつかの予想ができます。
- の素因数分解がであったとする。すべてのについて、の素因数分解におけるの指数がのそれ以上であれば、はサイクルに含まれる。そうでなければ、テールに含まれる。
- サイクルの長さはの約数である。
- サイクルに含まれる数ととの最大公約数は全て等しい。
証明
前提
1. と2. の証明
を素因数分解して現れる全ての素数について、に含まれるものをすべて掛け合わせたものをとし、とします。累乗の計算によって素数の種類数は変化しないので、とはの何乗かに依存しません。
予想1. での仮定から、です。よって、任意の非負整数に対してです。
また、の定義より、とは互いに素です。よって、フェルマーの小定理からが成り立ちます。両辺にを掛け合わせて、を得ます。
以上より、中国剰余定理を用いることでであることが示されました。さらに、予想2. のより強い主張として、「サイクルの長さはの約数である。」が得られました。
また、あるについて、の素因数分解におけるの指数がのそれ未満であるときを考えてみます。このとき、はで割り切れないためサイクル内のどの要素とも一致しません。よって、サイクルには含まれません。そして、を掛けていくことで一度サイクルに入ってしまうとそれ以降はサイクルから抜けることはありません。よってこのとき、はテールに含まれます。
3. の証明
先程の証明のを引き継ぎます。サイクルに含まれる任意の要素について、であり、とは互いに素です。よって、です。
実装への考察
テールの部分を別に調べて、そこで解が見つかれば終了、そうでなければサイクルをBSGSの要領で調べれば良さそうです。その際、BSGSで見つかった解にはテールの長さを足す必要があります。
テールの長さ
の素因数分解をとします。また、とします。予想1. の帰結より、がサイクルに含まれる条件は、全てのが存在してとなることです。よって、テールの長さはになります。
サイクルの性質
サイクルの長さをとします。サイクルの中の任意の数について、です。よって、このサイクルの単位元「のようなもの」はです。「のようなもの」という表現は、がこのサイクルの中に含まれていないことがあるためです(例:のとき、となりますが、これはサイクルに含まれません)。しかし、をかけていくといつかはサイクルの中に入ることと、もまたを満たすので、サイクルの中から単位元を見つけることができます。実装上はどちらでも問題ないです。の方が計算量は少ないですが、サイクルの中の要素で演算を閉じさせたいという場合はそのような要素を探すこともできます。
そして、「を掛ける」という操作の逆演算、つまり、サイクルの進む向きとは逆に進む操作を考えることができます。これがを掛けることによって実現するとしましょう。言い換えると、サイクル内の任意の数についてが成り立ちます。先程の単位元「のようなもの」を考えたときの式から推察できることとして、たとえば、すなわちとするといつでもこの等式が成り立ちます。このを逆元「のようなもの」と呼び、と表記します。
別の方法でこのを求める方法があります。単位元「のようなもの」をとします。満たすべき等式はです。ところで、競技プログラミングでおなじみのmod逆元というものがありますが、大きく2通りの求め方がありました。ひとつはフェルマーの小定理を使って求めるもの、もうひとつは拡張ユークリッドの互除法を使って求めるもの、です。で求めた方法はフェルマーの小定理的です。実は、拡張ユークリッドの互除法によってを求めることができます。
等式を満たす整数が存在する条件として、です。今回の場合、です。予想1. の証明で出てきたを用いると、はまたはに等しく、どちらもで割り切れます。よって、の存在が保証されたと同時に、拡張ユークリッドの互除法でを求めることができることがわかりました。
実装
実装方針
以上のことをまとめると、実装方針は次のようになります。
入力: : 整数
出力: を満たす最小の非負整数(存在しなければ)
- のとき、別で処理する。
- を素因数分解する。とする。
- 全てのについて、がで最大回割り切れるとして、となる最小のを求める。このがテールの長さで、とおく。
- について、となるがあれば、を返して終了。
- とする。もしがで割り切れなければ、を返して終了。
- サイクルの長さがの約数であることから、である最小の約数をさがす。このがサイクルの長さである。
- 以下、サイクル内の要素でBSGS。
計算量を見積もってみると、BSGSと素因数分解がネックとなり、です。
コード
verify
Discrete Logarithm:Library Checker
群論と離散対数のお時間です - 2
前回の続きです。一般化されたBSGSはまとまりをよくするために次回に回し、今回は、
- Pohlig-Hellman algorithm
- BSGS, Pohlig-Hellman algorithmの実装
について書いていきたいと思います。
Pohlig-Hellman algorithm
を素因数分解したときの指数が大きい場合に有効なアルゴリズムであるPohlig-Hellman algorithmを紹介します。ようやくここで群論が登場します。
ここでもとが互いに素である必要があります。ところで、これはが成り立つことを意味しますが、はを満たすものの中で最小ではない可能性があります。しかし、このはの約数であることが示せます。そこで、の約数のうちで等式を満たすような最小なものを探しておきます。そして、これが巡回群の位数となります。
以下、この巡回群の位数をとします。そして、のように素因数分解できるとします。ここで注意するべきなのは、今考えているのはあくまででの話題なので、ではなくでmodをとらなくてはなりません(私が実装でハマった原因はこれでした)。
まず、Pohlig-Hellman algorithmの特別な場合の部分問題として、のように、素因数分解してひとつの素数のみのべき乗の形になっている場合を考えます。解く問題としては、なるを求める、という問題です。位数がなので、解があるとすればこの範囲に必ずおさまることが保証されます。このは、と表せるので、このを求めたいです。
までが求まっているとして、とします(何も求まっていないときはとします)。このとき、方程式を変形してを得ることができます。この両辺を乗します。今考えている巡回群の位数はですから、となります。よって、左辺はのみ残ります。
ここで、とおくことで、です。の生成する巡回群の位数はであるので、これを満たすをBSGSで求めることができます。もし解が存在しなければその旨を報告し、終了です。そうでないとき、にを足し、を求める計算に進みます。
この操作をから順に行い、が求める答えになります。で、各あたりの計算量はなので、全体的な計算量はです。
もとの問題に戻ります。位数がと表され、はの巡回群を生成します。同時に、とします。問題の方程式の両辺を乗することで、方程式を得ます。この方程式は先程の部分問題の解き方で解くことができます。また、この方程式の解をとすると、これはのでの値になります。
そうして得られた位数での方程式の解をもとに、中国剰余定理からを求めることができます。
全体の計算量はです。
実装例
BSGSの実装例
def divisors(n): # nの約数 L = [] p = 1 while p * p <= n: if n % p == 0: L.append(p) p += 1 for i in range(len(L), 0, -1): if L[i-1] * L[i-1] != n: L.append(n // L[i-1]) return L # nの約数が昇順に並んでいる。 def totient(n): # nと互いに素なn以下の正整数の個数 a = n p = 2 res = n if a % p == 0: res >>= 1 while 1 - a % 2: a >>= 1 p += 1 while a != 1: if a % p == 0: res //= p res *= p - 1 while a % p == 0: a //= p p += 2 if p * p > n and a != 1: res //= a res *= a - 1 break return res def babystep_giantstep(n, a, b): # a^k = b (mod n)なる最小の非負整数kを求める(存在しなければ-1)。 baby_step = {} candidate = divisors(totient(n)) # 巡回群の位数の候補 pnt = 0 while pow(a, candidate[pnt], n) != 1: # a^k = 1 (mod n)となる最小のkを探す。それが巡回群の位数となる。 pnt += 1 if pnt == len(candidate): return -1 # nとaが互いに素でない(一般化した時に扱う)。 p = candidate[pnt] # 巡回群の位数 l, r = 0, p m = (l + r) // 2 # m = ceil(sqrt(p)) while r - l > 1: if m * m <= p: l = m else: r = m m = (l + r) // 2 if m * m < p: m += 1 x, y, u, v, k, l = 1, 0, 0, 1, a, n while l: x, y, u, v, k, l = u, v, x - u * (k // l), y - v * (k // l), l, k % l bas = pow(x, m, n) # bas = a^(-m) (mod n) e = 1 for i in range(m): # baby_stepに(a^i, i)を記録していく。 baby_step[e] = i e *= a e %= n y = b for i in range(m): if y in baby_step: return i * m + baby_step[y] # これが最小の解 y *= bas # giantstep y %= n return -1 # 見つからなければ-1を返す。
Pohlig-Hellman algorithmの実装例
def divisors(n): # nの約数 L = [] p = 1 while p * p <= n: if n % p == 0: L.append(p) p += 1 for i in range(len(L), 0, -1): if L[i-1] * L[i-1] != n: L.append(n // L[i-1]) return L # nの約数が昇順に並んでいる。 def totient(n): # nと互いに素なn以下の正整数の個数 a = n p = 2 res = n if a % p == 0: res >>= 1 while 1 - a % 2: a >>= 1 p += 1 while a != 1: if a % p == 0: res //= p res *= p - 1 while a % p == 0: a //= p p += 2 if p * p > n and a != 1: res //= a res *= a - 1 break return res def babystep_giantstep(n, a, b): # a^k = b (mod n)なる最小の非負整数kを求める(存在しなければ-1)。 baby_step = {} candidate = divisors(n-1) # 巡回群の位数の候補 pnt = 0 while pow(a, candidate[pnt], n) != 1: # a^k = 1 (mod n)となる最小のkを探す。それが巡回群の位数となる。 pnt += 1 if pnt == len(candidate): return -1 # nとaが互いに素でない(一般化した時に扱う)。 p = candidate[pnt] # 巡回群の位数 l, r = 0, p m = (l + r) // 2 # m = ceil(sqrt(p)) while r - l > 1: if m * m <= p: l = m else: r = m m = (l + r) // 2 if m * m < p: m += 1 x, y, u, v, k, l = 1, 0, 0, 1, a, n while l: x, y, u, v, k, l = u, v, x - u * (k // l), y - v * (k // l), l, k % l bas = pow(x, m, n) # bas = a^(-m) (mod n) e = 1 for i in range(m): # baby_stepに(a^i, i)を記録していく。同じkeyは小さい方をとる。 if e not in baby_step: baby_step[e] = i e *= a e %= n y = b for i in range(m): if y in baby_step: return i * m + baby_step[y] # これが最小の解 y *= bas # giantstep y %= n return -1 # 見つからなければ-1を返す。 def extgcd(a, b, c): # ax + by = cを満たす整数の組(x, y)を探す。 if b == 0: if a == 0: if c == 0: return 0, 0 return None if c % a == 0: return c // a, 0 return None if b < 0: a, b, c = -a, -b, -c tk, tl = a, b while tl: tk, tl = tl, tk % tl if c % tk: return None a //= tk b //= tk c //= tk x, y, u, v, k, l = 1, 0, 0, 1, a, b while l: x, y, u, v = u, v, x - u * (k // l), y - v * (k // l) k, l = l, k % l x *= c k = x // b x -= k * b y *= c y += k * a return x, y def CRT(num, a_list, m_list): # 複数modの条件を満たす整数を見つける。 r = 0 bas = 1 x, y = 0, 0 for i in range(num): x, y = extgcd(bas, -m_list[i], a_list[i]-r) r += bas * x bas *= m_list[i] return r % bas def pohlig_hellman(mod, a, b): candidate = divisors(totient(mod)) # 巡回群の位数の候補 pnt = 0 while pow(a, candidate[pnt], mod) != 1: # a^k = 1 (mod)となる最小のkを探す。それが巡回群の位数となる。 pnt += 1 if pnt == len(candidate): return -1 # nとaが互いに素でない(一般化した時に扱う)。 n = candidate[pnt] # 巡回群の位数 factors = [] # nの素因数分解 res_seed = [] # 素数のべき乗での答えを格納 mod_list = [] r = 0 cnt = 0 tmp = n # 素因数分解パート while tmp % 2 == 0: cnt += 1 tmp >>= 1 if cnt: r += 1 res_seed.append(0) mod_list.append(0) factors.append((2, cnt)) pr = 3 while tmp != 1: cnt = 0 while tmp % pr == 0: tmp //= pr cnt += 1 if cnt: r += 1 res_seed.append(0) mod_list.append(0) factors.append((pr, cnt)) pr += 2 if pr * pr > n and tmp != 1: res_seed.append(0) mod_list.append(0) r += 1 factors.append((tmp, 1)) break # 素数のべき乗についての部分問題を解く。 for i in range(r): p = factors[i][0] e = factors[i][1] p_i = pow(p, e) mod_list[i] = p_i g = pow(a, n//p_i, mod) # gは位数p^eの群をなす。 h = pow(b, n//p_i, mod) # g^res_seed[i] = h (mod) となるres_seed[i]を求める。 # res_seed[i] = x_0 + x_1 * p + ... + x_{e-1} * p^{e-1} (0 <= x_j < p)の形で表せる。 bas = 1 inv_g = extgcd(g, mod, 1)[0] # gの逆元 gamma = pow(g, pow(p, e-1), mod) # これは位数pの巡回群をなす。 for k in range(e): # (x_0, x_1, .... x_{k-1})までわかっているとする。 # 式変形により、g^(x_k*p^k+...+x_{e-1}*p^{e-1}) = h * g^(-(x_0+x_1*p+...+x_{k-1}*p^{k-1})) # 両辺p^{e-k-1}することで、gamma^(x_k) = ...の形になるので、x_kをbsgsで求める。 hk = pow(h*pow(inv_g, res_seed[i], mod)%mod, pow(p, e-k-1), mod) xk = babystep_giantstep(mod, gamma, hk) if xk < 0: return -1 # x_kが見つからなければ-1 res_seed[i] += xk * bas bas *= p # CRTでそれぞれの部分問題の答えをまとめる。 res = CRT(r, res_seed, mod_list) return res
verify
atcoder.jp とが固定なので、辞書や位数を一度計算してメモしておくように実装すると、計算量を減らすことができます。
群論と離散対数のお時間です - 1
BSGSを知っていますか?私は知りません。
ということでBSGSのWikipediaを読み漁っていたら、何やらさらにつよいPohlig-Hellman's algorithmなるアルゴリズムがあるじゃないですか!ちょうどその数日前にSA-ISを実装して調子に乗っていたため、アルゴリズムがわりとシンプルじゃけん実装できるじゃろ!と思い実装したところ、世の中はそんなに甘くなく、めちゃくちゃバグり散らしました。世の中ってどうしたら甘くなるんでしょうね。この世界に真っ赤なジャムでも塗れば良いんでしょうか。ところで私ははちみつ派です。おすすめのはちみつがあればご一報ください。
さて、このアルゴリズムですが、なにやら群論の知識をたくさん使うらしいので、メモ化再帰的にわかるところまで進んでから一つずつ回収していく、という手法で理解しようと思いました。また、一応私は大学での専攻が物理学で、世の中の99.7%くらいの人には理解されないので詳細は省きますが、群論など抽象的な代数学を多用する分野に興味があるので、まとめてやっちゃおう、という勢いでやってます。個人用のメモでも良かったんですが、世の中には私と同じようにBSGSを一般化したい人がいるかもしれないと思い、公開します。
材料としてはおもに昔受けた群論に関する講義での手書きの講義ノートです。このこともデジタルな文書として残しておきたい理由で、私のページをケチる癖でよくわからないことになっている部分があるのと、後で見返したときにページ内検索ができるためです。この記事を読んでいる若い学生さんはちゃんと整理整頓されたわかりやすいノートを書きましょうね。
目標
- ラグランジュの定理についてやる。
- Pohlig-Hellman's algorithm を実装する。
基本
定義がいっぱいです。
群の公理
群の公理は以下のようなものです。を群とし、の元の積を単にと書くことにします。
群の一例として、整数の集合に群の演算として足し算の構造を入れたものがあります(どの隣り合う二つの数の足し算から実行しても結果は同じで、単位元0と整数に対する逆元があります)。また、1.と2.のみを満たすものを半群、1.と2.と3.のみを満たすものをモノイドといいます。モノイドはセグ木に乗ることでよく知られていますね。行列の集合に積の演算を入れたものはモノイドの一例です。
部分群
群の部分集合であって、の演算で群のとき、はの部分群であるといいます。
群の生成系
群の部分集合であって、からいくつか(無限個でもよい)元をとってきたものを考えます。つまり、です。の生成するの部分群とは、この集合の(重複を許して)有限個の元およびその逆元の積全体からなる集合、すなわち、を選んだ元の個数として、がなす集合です。この集合は、と書かれます。
すこし説明が難しいので例を考えます。とすると、であるので任意の整数についてを満たす整数が存在します。よって、はを生成します。ここで、のように、同じ元を複数回選んでも良いです。
巡回群
一つの元から生成される群は巡回群とよばれます。そして、もし となるような最小の自然数が存在するなら、このを位数といい、は位数の有限巡回群といいます。存在しなければ無限巡回群と呼ばれます。
有限巡回群の例として、上での足し算があります。を個足していくと単位元になります。
群準同型
な写像を考えます。例によっては群です。そして、に対しとなるとき、は準同型写像であるといい、このような写像が存在すればとは群準同型であるとします。
準同型写像というのはどういうものかを説明します。もともと群には二項演算が入っています。これは、に対しという群の要素を定めるので、この二項演算はです。さらに、写像によってこの要素を写すと、なのでです。つまり、の二つの要素に対して二項演算をしてから写像でに写す、という操作です。この操作はまとめるとです。
一方、の要素を写像で写してからそれらの二項演算を行ってもとなります。このとき、に対しとなります。つまり、が準同型写像であれば、この2通りの結果が等しいということなのです。
さらに、この写像が全単射であれば、は同型写像、または、とが同型であるといい、と表されます。
同値類
集合についてというような関係を二項関係といい、に対して以下の3つを満たす二項関係を同値関係といいます。
- (反射律)
- (対称律)
- (推移律)
例えば等号のはこの条件を満たします。意味的にはもっと広く、「とがある基準を以って仲間ですか」という質問にYesであれば同値、Noであれば同値ではない、という感じです。以降、同値関係はで表します。
そして、集合に対して、と定義します。このは、と同値なの要素の集合で、同値類と呼ばれます。そして、からなる集合をに対する商集合といい、と表します。
剰余類
を群として、をの部分群とします。そして、の元に対し、であるとします。この二項関係が同値なことは非自明ですが、がんばって説明してください。そしてあなたが大嫌いな人に高らかに自慢しましょう。
ここで、がどういう集合かを考えてみたいと思います。
まず、となります。同値類の定義は、と同値なものの集合という定義でした。はの要素であるので、であることがわかります。これは、の各要素の左からをかけたものを集めたもの、と解釈できます。そして、このをと表し、を要素とした商集合はと書かれ、左剰余類と呼びます。右剰余類も似たように定義できます。の要素数は、におけるの指数、といいます。
例として、の整数の集合にの足し算の構造を入れたものを考えます。これは群であることがわかると思います。この部分群として、集合にの足し算の構造を入れたものを考えられます。すると、として、左剰余類はです。
ところで、という二つの元が同値でなかったとき、の関係性はどうなるでしょうか。1300234241で割ったあまりを求めてください。具体的には、この二つの集合の共通部分はどうなるでしょう。そこで、が共通部分をもつ、つまり、についてなる要素があると仮定します。は部分群の要素なので、に逆元を持ちます。よってです。そして、という積はハンターハンターなのでなので、のある元です。この元をとおくと、ですので、です。ですが、これはが同値でないという仮定に反します。よって、は共通部分をもちません。
ラグランジュの定理
一応、準備は整ったので、本題のひとつ「ラグランジュの定理」の証明と応用に移ります。
定理の内容
を有限群とし、をの部分群としたときに、の位数はの位数の約数である。より具体的には、の位数はの要素数(におけるの指数)との位数を掛け合わせたものである。
証明
とします。の位数をとして、とします。すると、左剰余類です。また、をという写像とします。
まず、任意ののについてであれば、両辺にを作用させることでが言えるため、は単射です。
加えて、はの要素に左からをかけて作った集合なので、であるはずです(そうでなければ私が病み散らかしてしまいます)。よって、は全射です。
以上より、が全単射であることがわかりました。よってとが同型であることから、の要素数はです。
さて、の互いに同値でないものを集めた集合を、要素数をとしてとします。とはそもそもの部分集合ですから、のどの元もに含まれています。そして、類別を行うときに全ての元の対で同値関係を決めたため、の元であってどのにも含まれていないものはありません。つまり、のどの元ものどこかに属しています。したがって、 = です。
また、先に示したように、任意のについての要素数はです。さらに、剰余類のところで見たように、任意のについてです。の位数は包除原理を用いるまでもなくただそれぞれの剰余類の要素数を足し合わせれば良いです。
以上より、の位数はであるので、定理は示されました。
応用
では、ラグランジュの定理を利用して何ができるかをみていきたいと思います。
巡回群について
が生成する巡回群はの部分群です。これにラグランジュの定理を適用すると、の位数はの位数の約数であることが言えます。このことから、よりです。
フェルマーの小定理
フェルマーの小定理とは、素数と、と互いに素な整数について、であるというものです。二項定理を用いた証明が簡単ですが、ラグランジュの定理を用いても示せます。
が素数であることからのすべての数はと互いに素です。このことは、での逆元の存在を保証します。を、を含む剰余類とすると、の倍数を除いた集合は上の積で群となり、と類別されます。この位数はなので、です。
フェルマーの小定理はよく知られているようにさらに一般化できます。一般の以上の整数と、と互いに素な整数について、以下の正の整数でと互いに素なものの個数を用いてです。これも同じようにラグランジュの定理を用いて示せます。ただし、今度は未満の正の整数でと互いに素ではないものがあるかもしれないので、それを除いた剰余類で群を作らなくてはなりません。これが指数のの由来です。
小休止
あー疲れた。群論つら。ちなみにこの記事をここまで書くのに3~4日くらいかかりました。
ところでおすすめの原宿系ファッションのお店の情報は随時お待ちしております。どんな方法でも情報送ってくれたら僕が喜びます。
離散対数
問題
正の整数と、以上以下の整数が与えられる。を満たす最小の非負整数が存在すればそれを求め、存在しなければその旨を報告せよ。
Baby-step giant-step(BSGS)
でに逆元があるとき、すなわち、とが互いに素のときは、baby-step giant-step(略してBSGS)というアルゴリズムが使用できます。
とが互いに素であれば、剰余類はフェルマーの小定理の証明に出てきた群の要素です。よって、問題の等式を満たすが存在するなら、それは以下の非負整数のどれかです。しかし、なので、まともに全探索したら間に合いません(無理矢理高速化したりするとコンピュータが泣いてショートしてしまいます)。
そこで、として、とおいてみます。すると、問題の等式はと変形できます。ここで、とが互いに素であることから、に逆元が存在することを使いました。
ここで、あらかじめについてとなるような最小のを求めておくことで、もしなるが存在すればが答えになります。ちょっとわかりにくいですが、以下に示す実装の手順を見ればわかると思います。
【実装の手順】
入力:
出力:(等式を満たす最小の非負整数、存在しなければ)
- を求める。
- とする。
- 辞書などを用いてについて組を持っておく。
- について、なるが存在すればを返して終了。
- 終了しなければ、を返して終了。
実装上の注意点として、辞書に値を格納するときに同じkeyに対して異なるが複数対応することがあり、そのときの昇順のループではの最大値で更新されてしまいます。の降順でループするか、すでにkeyが辞書に入っているときは値の更新をしない、などの方法で対応できます。
時間計算量、空間計算量ともにです。
お知らせ
すみません、ここにPohlig-Hellman algorithmの解説を書く予定でしたが、ちょっと想定より長くなったので次回の自分に投げます。次回は
- Pohlig-Hellman algorithm
- 一般modでの離散対数の考察
について書く予定です。余裕があれば具体的な実装を書く予定ですが、分量が多くなればまた次回以降に回します。