よすぽの日記 2024-03-26T01:02:51+09:00 yosupo Hatena::Blog hatenablog://blog/11696248318755814887 高速剰余算 div2by1 実装してみた hatenablog://entry/6801883189093613965 2024-03-26T01:02:51+09:00 2024-03-26T01:21:53+09:00 div2by1というアルゴリズムがある -> https://gmplib.org/~tege/division-paper.pdf これはBarrett reductionやMontgomery乗算と違い、 (k, k) -> 2k-bitの乗算器でk-bitの剰余算ができる(Barrett reductionは(2k, 2k) -> 4k-bitの乗算器を必要とする) modが偶数でも動作する(Montgomery乗算はmodが奇数の必要がある) という二つの性質を持つ。今回は次の6種類のプログラムを実装し、$(8 \times 10^{7})! \bmod 998244353$を計算して… <p>div2by1というアルゴリズムがある -> <a href="https://gmplib.org/~tege/division-paper.pdf">https://gmplib.org/~tege/division-paper.pdf</a></p> <p>これはBarrett reductionやMontgomery乗算と違い、</p> <ul> <li>(k, k) -> 2k-bitの乗算器でk-bitの剰余算ができる(Barrett reductionは(2k, 2k) -> 4k-bitの乗算器を必要とする)</li> <li>modが偶数でも動作する(Montgomery乗算はmodが奇数の必要がある)</li> </ul> <p>という二つの性質を持つ。今回は次の6種類のプログラムを実装し、$(8 \times 10^{7})! \bmod 998244353$を計算して速度を比較した。</p> <ul> <li>32-bit Montgomery</li> <li>32-bit div2by1</li> <li>mod &lt; $2^{30}$という制約を仮定し32-bit div2by1をちょっと改造したもの</li> <li>これら三種のアルゴリズムをAVX2で高速化したもの</li> </ul> <p>ベンチマークのコード、およびdiv2by1改造版のpythonコードについては、<a href="https://gist.github.com/yosupo06/e616e2dd1fd59bfa356d26135ca981a8">div2by1 &#x6539;&#x9020;&#x7248; &middot; GitHub</a></p> <p>手元(Ryzen 5 5600X)、およびAtCoderのコードテストでの実行時間は次の通り。</p> <table> <thead> <tr> <th></th> <th>Local</th> <th>AtCoder</th> </tr> </thead> <tbody> <tr> <td>naive</td> <td>285ms</td> <td>482ms</td> </tr> <tr> <td>naive(const mod)</td> <td>227ms</td> <td>298ms</td> </tr> <tr> <td>montgomery</td> <td>202ms</td> <td>257ms</td> </tr> <tr> <td>montgomery AVX</td> <td>28ms</td> <td>51ms</td> </tr> <tr> <td>div2by1</td> <td>316ms</td> <td>458ms</td> </tr> <tr> <td>div2by1 AVX</td> <td>42ms</td> <td>107ms</td> </tr> <tr> <td>my div2by1</td> <td>292ms</td> <td>383ms</td> </tr> <tr> <td>my div2by1 AVX</td> <td>52ms</td> <td>83ms</td> </tr> </tbody> </table> <p>次のようなことがわかる</p> <ul> <li>そもそもnaive(=除算命令を使っているはず)が直観よりかなり速い。おそらくIce Lakeから除算が速くなったというやつ(<a href="https://lcstmarck.hatenablog.com/entry/2019/10/11/231813">Intel Ice Lake&#x306E;&#x30D7;&#x30ED;&#x30BB;&#x30C3;&#x30B5;&#x306F;&#x6574;&#x6570;&#x9664;&#x7B97;&#x547D;&#x4EE4;&#x304C;&#x30A2;&#x30C4;&#x3044; - chroot(&quot;/home/hibari&quot;)</a>)。でもIce lakeの製造開始は2019年らしい、老人さん?笑</li> <li>montgomery + AVX2が速すぎる。4倍速以上になるとは思ってなかった。</li> <li>div2by1 あんまり速くない。montgomery速すぎ + AVXで高速化とかするならもうmod 998244353決め打ちで問題なさそう であることを考えると正直出番がなさそうな</li> </ul> yosupo 区間mul 区間積 O(log N) hatenablog://entry/6801883189092053320 2024-03-19T21:33:39+09:00 2024-03-19T21:33:58+09:00 問題 長さNの整数列a_iが与えられます $Q$個のクエリを処理してください given l, r, x: $a_l \cdots a_r$を$x$倍 given l, r: $a_l \times a_{l+1} \times \cdots \times a_r \bmod 998244353$を出力 $O(\log N)$ per queryで解けるがおそらく$O( \log^{2} N)$と識別不可能。 ちなみに元ネタはこれ(解法は違う): 区間代入/区間積 Θ(logN)/query - noshi91のメモ $O(\log^{2} N)$ 解法 普通に遅延伝搬segtreeに乗せる。… <h2 id="問題">問題</h2> <p>長さNの整数列a_iが与えられます $Q$個のクエリを処理してください</p> <ul> <li>given l, r, x: $a_l \cdots a_r$を$x$倍</li> <li>given l, r: $a_l \times a_{l+1} \times \cdots \times a_r \bmod 998244353$を出力</li> </ul> <p>$O(\log N)$ per queryで解けるがおそらく$O( \log^{2} N)$と識別不可能。</p> <p>ちなみに元ネタはこれ(解法は違う): <a href="https://noshi91.hatenablog.com/entry/2019/10/05/203704">&#x533A;&#x9593;&#x4EE3;&#x5165;/&#x533A;&#x9593;&#x7A4D; &Theta;(logN)/query - noshi91&#x306E;&#x30E1;&#x30E2;</a></p> <h2 id="Olog2-N-解法">$O(\log^{2} N)$ 解法</h2> <p>普通に遅延伝搬segtreeに乗せる。ノードには区間の総積と区間の長さを乗せる。ACL風に書くと<code>S = pair&lt;modint, int&gt;</code></p> <p>作用(<code>mapping</code>)の中でpow_modを呼ばないといけないため$O(\log^{2} N)$になる</p> <h2 id="Olog-N-解法">$O(\log N)$ 解法</h2> <p>作用が可換なので遅延伝搬しない遅延伝搬segtree(何て呼ばれてるんでしょうこれ)が使える。 遅延伝搬しないverは次のようになる。もちろん素直に実装すると$O(\log^{2} N)$になるのだが、よく考えるとどちらも$O(\log N)$になる。</p> <p>ノードごとに2つのmodint a, bを持つ。初期値はaが区間の総積でbが1</p> <ul> <li>mul: [l, r]をsegtreeの区間に分割する。分割された区間、およびその区間を子孫に持つすべての区間について<code>b *= x^([l, r]と自分の区間の共通部分の面積)</code></li> <li>prod: [l, r]をsegtreeの区間に分割する。分割された区間それぞれについて、自分と先祖のbのprodを求め、cとする。そして<code>a * c^(区間の長さ)</code>を求める。これをすべての区間について掛け合わせる</li> </ul> <p>mulについては次のように高速化する。</p> <ul> <li>[l, r]を分割した区間: bにかかる係数は<code>x^(2^i)</code>の形になっているので、まとめて$O(\log N)$で前計算できる</li> <li>それ以外の区間: 子のbにかかる係数の積を自分のbに掛ければよい</li> </ul> <p>prodについては次のように高速化する。</p> <ul> <li>cについてはdfsしながらまとめて計算できるので、結局ある数列$d_1, d_2, ..., d_k (k = \log N)$について $d_1 \times d_2^{2} \times d_3^{4} \cdots$ が求められれば良い。これは $d_1 \times square(d_2 \times square(d_3 \times ...)))$という形で計算すれば $O(\log N)$</li> </ul> yosupo AHC030 環境構築 振り返り hatenablog://entry/6801883189084836364 2024-02-21T04:40:22+09:00 2024-02-21T04:42:31+09:00 AHC030に出て、11位でした。 自分は長期マラソンは殆どやったことがなく、2015年に3回topcoder MMに出たのが最初で最後(のはず)でした。 なので今回のAHCで環境整備系も全て一からやることになりました。なのであえてそちらについての感想や振り返りを書きます。 コンテスト中に使ったもの、また時間があったら欲しかったもの、を個人的に重要だと感じた順番で書いています。 ローカルテスター Psyhoさんも言っています(https://twitter.com/FakePsyho/status/1605639454600806401)が、ローカルテスターが一番重要な環境整備要素でした。自分… <p>AHC030に出て、11位でした。</p> <p>自分は長期マラソンは殆どやったことがなく、2015年に3回topcoder MMに出たのが最初で最後(のはず)でした。 なので今回のAHCで環境整備系も全て一からやることになりました。なのであえてそちらについての感想や振り返りを書きます。</p> <p>コンテスト中に使ったもの、また時間があったら欲しかったもの、を個人的に重要だと感じた順番で書いています。</p> <h2 id="ローカルテスター">ローカルテスター</h2> <p>Psyhoさんも言っています(<a href="https://twitter.com/FakePsyho/status/1605639454600806401">https://twitter.com/FakePsyho/status/1605639454600806401</a>)が、ローカルテスターが一番重要な環境整備要素でした。自分は適当な100行程度のpythonコードを準備して、改変しながら使っていました。</p> <ul> <li>絶対スコア / 相対スコアの一覧を(csv)出力する</li> <li>実行時間の一覧を出力する</li> <li>ケースをMでフィルターする</li> </ul> <p>の3つの機能を含んでいて、今回の問題だとこれらは必須だと思います。</p> <p>また、</p> <ul> <li>テストケースの並列実行機能</li> </ul> <p>は結局実装しなかったのですが(なんで?)、必須級だと思います。3秒 * 100ケース回すと5分待ちなので、効率がすごい落ちた自覚があります。</p> <p>個人的には複数の問題に対応できる強力なものを整備するよりは、pythonコードやらなんやらを問題ごとに毎回改変していくのが楽そうだなぁと思っています</p> <h2 id="Jupyter-notebook-IPython-notebook">Jupyter notebook (IPython notebook)</h2> <p>ちょっとした実験をすぐ書けて、プロットも出来るので非常に便利なツールでした。自分は今回は</p> <ul> <li>いろんなk, vで正規分布をプロットしてみる</li> <li>テストケースのMの分布を調べる</li> <li>その他細かい実験/計算</li> </ul> <p>に使いました。インストールが簡単(vscodeならプラグイン入れるだけ)なので使い得だと思います。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20240221/20240221040348.png" width="732" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span> <span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20240221/20240221041918.png" width="854" height="1200" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="でかつよCPU">でかつよCPU</h2> <p>先述の並列テストケース実行とも関係して、CPUは多ければ多いほどいいと思いました。先述のように自分の用意したローカルテスターは並列実行しないのですが、最終盤は結局そのローカルテスターをいろんなMに対して並列に走らせる、とかやっていてCPUが足りない状態になりました。</p> <p>クラウドにデカいインスタンス借りてsshするのが"正解"なのは間違いないのですが、インスタンス立てたり落としたりするのがどうしても億劫な気持ちになってしまい… 手元のPCが強ければそれが一番良いのは間違いないと思います</p> <h2 id="でかつよクラウドインスタンス">でかつよクラウドインスタンス</h2> <p>上と同じ話です。最終盤は強いCPUか強いクラウドインスタンスのどちらかは欲しい</p> <h2 id="google-spread-sheet">google spread sheet</h2> <p>先述のローカルテスターの出力を張ってスコアの比較に使っていました。 最初のほうは快適だったのですが、画像のシート一覧を見るとわかるように終盤になるにつれて限界になっていきました。改善の余地がありそうです(がspread sheet職人はやりたくない うーん)</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20240221/20240221041601.png" width="1200" height="794" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <h2 id="順位表のスナップショット機能-未実装">順位表のスナップショット機能 (未実装)</h2> <p>今回の問題は相対スコアなので、順位表のスコアが常時変動します。特に「自分の提出で他の人の点数がどのぐらい変わったか」は、自分の提出がbestを含むかを含んでおり、重要な情報でした。</p> <p>そのため、提出直前の順位表を保存しておく必要があったのですが、注意力が低く複数回失敗しました。</p> <p>考えられる対策としては定期的に順位表のスナップショットを取るスクリプトを走らせればいいです。でもどのぐらいの頻度なら怒られないのか、とか、そもそも参加者がみんな個人で定期的にスナップショットを取るというのは変な話なので、公式でスナップショットを提供してくれたら嬉しいなぁと思っています。</p> yosupo マージテクの逆でよく出てくる"2個の木のうち小さいほうを探す"処理ってcoroutineと相性がいいよね hatenablog://entry/6801883189074628217 2024-01-13T08:03:50+09:00 2024-01-13T08:03:50+09:00 背景 次のような問題を考えます 2個の木が与えられます。部分木の頂点数を$n, m$とした時に、$O(\min{(n, m)})$時間で小さいほうの部分木の頂点を列挙してください。 このような問題は「データ構造をマージする一般的なテクの逆」などと呼ばれるテクニックを使う問題で出てきます。具体例としては I - 盆栽 が一番有名だと思います。 冒頭の問題ですが、解法自体は対して難しくなく、「2つの木に並列にBFS/DFSして、どちらかが終わったら打ち切ればいい」というだけの話です。ですがいざ実装をしようとするとなかなか面倒です。しかもウニグラフ等で計算量が壊れがちだったりして厄介です。 実はこの… <h1 id="背景">背景</h1> <p>次のような問題を考えます</p> <ul> <li>2個の木が与えられます。部分木の頂点数を$n, m$とした時に、$O(\min{(n, m)})$時間で小さいほうの部分木の頂点を列挙してください。</li> </ul> <p>このような問題は「データ構造をマージする一般的なテクの逆」などと呼ばれるテクニックを使う問題で出てきます。具体例としては <a href="https://atcoder.jp/contests/utpc2014/tasks/utpc2014_i">I - &#x76C6;&#x683D;</a> が一番有名だと思います。</p> <p>冒頭の問題ですが、解法自体は対して難しくなく、「2つの木に並列にBFS/DFSして、どちらかが終わったら打ち切ればいい」というだけの話です。ですがいざ実装をしようとするとなかなか面倒です。しかもウニグラフ等で計算量が壊れがちだったりして厄介です。</p> <p>実はこの実装はcoroutineと呼ばれる概念と相性が良いです。coroutineはC++だとC++20で入った機能 + 主な用途が並列処理やI/O bottleneckの処理等なので、おそらく競プロでの知名度は低いと思いますが、大体の新しめの言語には実装されている機能です。</p> <p>実際に冒頭の問題を実装することを考えます。まず、$O(\max{(n, m)})$時間かけていいときの実装例を示します。ただ愚直にdfsをしているだけです。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synStatement">using</span> Tree = <span class="synType">vector</span>&lt;<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&gt;; <span class="synType">void</span> <span class="synIdentifier">list_vertex</span>(<span class="synType">const</span> Tree&amp; tree, <span class="synType">int</span> u, <span class="synType">int</span> p, <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&amp; result) { result.<span class="synIdentifier">push_back</span>(u); <span class="synStatement">for</span> (<span class="synType">int</span> v : tree[u]) { <span class="synStatement">if</span> (v == p) <span class="synStatement">continue</span>; <span class="synIdentifier">list_vertex</span>(tree, v, p, result); } } <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; <span class="synIdentifier">small_tree_vertex</span>(<span class="synType">const</span> Tree&amp; tree1, <span class="synType">const</span> Tree&amp; tree2) { <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; result1, result2; <span class="synIdentifier">list_vertex</span>(tree1, <span class="synConstant">0</span>, -<span class="synConstant">1</span>, result1); <span class="synIdentifier">list_vertex</span>(tree2, <span class="synConstant">0</span>, -<span class="synConstant">1</span>, result2); <span class="synStatement">if</span> (result1.<span class="synIdentifier">size</span>() &lt; result2.<span class="synIdentifier">size</span>()) { <span class="synStatement">return</span> result1; } <span class="synStatement">else</span> { <span class="synStatement">return</span> result2; } } </pre> <p>これをcoroutineを使って実装すると次のようになります。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synStatement">using</span> Tree = <span class="synType">vector</span>&lt;<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&gt;; <span class="synComment">// https://github.com/lewissbaker/cppcoro/blob/master/include/cppcoro/recursive_generator.hpp</span> cppcoro::recursive_generator&lt;<span class="synType">int</span>&gt; <span class="synIdentifier">list_vertex</span>(<span class="synType">const</span> Tree&amp; tree, <span class="synType">int</span> u, <span class="synType">int</span> p) { co_yield u; <span class="synStatement">for</span> (<span class="synType">int</span> v : tree[u]) { <span class="synStatement">if</span> (v == p) <span class="synStatement">continue</span>; co_yield <span class="synIdentifier">list_vertex</span>(tree, v, p); } } <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; <span class="synIdentifier">small_tree_vertex</span>(<span class="synType">const</span> Tree&amp; tree1, <span class="synType">const</span> Tree&amp; tree2) { <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; result1, result2; <span class="synType">auto</span> co1 = <span class="synIdentifier">list_vertex</span>(tree1, <span class="synConstant">0</span>, -<span class="synConstant">1</span>); <span class="synType">auto</span> co2 = <span class="synIdentifier">list_vertex</span>(tree2, <span class="synConstant">0</span>, -<span class="synConstant">1</span>); <span class="synStatement">for</span> (<span class="synType">auto</span> it1 = co1.<span class="synIdentifier">begin</span>(), it2 = co2.<span class="synIdentifier">begin</span>();; it1++, it2++) { <span class="synStatement">if</span> (it1 == co1.<span class="synIdentifier">end</span>()) <span class="synStatement">return</span> result1; <span class="synStatement">if</span> (it2 == co2.<span class="synIdentifier">end</span>()) <span class="synStatement">return</span> result2; result1.<span class="synIdentifier">push_back</span>(*it1); result2.<span class="synIdentifier">push_back</span>(*it2); } } </pre> <p>少し<code>list_small_tree_vertex</code>がごちゃごちゃしましたが、これで $O(\min{(n, m)})$ 時間で動作します。並列BFSを実装したことがあればなかなか驚きの実装量だと思います。また、C++23ならば<code>std::views::zip</code>を使えばより簡潔な実装になるはずです。</p> <p>coroutineというのは、ざっくり言うと「途中で中断と再開」が可能な関数です。実際に、新しい<code>list_vertex</code>関数は、「頂点を見つけたら(= <code>co_yield u</code>にたどり着いたら)その頂点を返して関数を中断、そして<code>it++</code>が呼ばれたらdfsをそこから再開」という挙動をします。なので、<code>list_vertex</code>の帰り値を普通のイテレーターのように扱い、どちらかのイテレーターが末尾に到達したらそこまでの結果を返すだけでよいです。</p> <p>なお、C++だと再帰関数をcoroutineにするには<code>cppcoro::recursive_generator</code>のような追加実装が必要なようですが、MITライセンスで公開されているので適切にやれば自分で実装しなくても大丈夫です。</p> <p>実際に盆栽を解いたコードはこちらです: <a href="https://atcoder.jp/contests/utpc2014/submissions/49240549">Submission #49240549 - &#x6771;&#x4EAC;&#x5927;&#x5B66;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;&#x30B3;&#x30F3;&#x30C6;&#x30B9;&#x30C8;2014</a> 。冒頭(286行目まで)にこの<code>recursive_generator</code>が張り付けられているのでウォっとなりますが、それ以降だけ見ると結構簡潔ではないでしょうか。</p> yosupo -march=native 諸々 hatenablog://entry/6801883189070037841 2023-12-27T03:11:40+09:00 2023-12-27T03:12:31+09:00 概要 -march=nativeについて色々調べた。 話題の発端 こちらのツイートであると思われる。 分からんこれ何かの未定義動作踏んでる?? pic.twitter.com/ACV8fb8PjQ— AllDirections (@AllDirections4) 2023年12月21日 シンプルなコードで、しかも-march=nativeを付けた場合のみ壊れる、ということで非常に力がある。自分もこれを機にmarchについて調べてしまった。 そもそもなぜ上記のコードは壊れているのか? 元のコードからC++要素を取り除くと次のようになる。もちろんこのコードも壊れていることがコードテストから確認でき… <h2 id="概要">概要</h2> <p>-march=nativeについて色々調べた。</p> <h2 id="話題の発端">話題の発端</h2> <p>こちらのツイートであると思われる。 <blockquote data-conversation="none" class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">分からん<br>これ何かの未定義動作踏んでる?? <a href="https://t.co/ACV8fb8PjQ">pic.twitter.com/ACV8fb8PjQ</a></p>&mdash; AllDirections (@AllDirections4) <a href="https://twitter.com/AllDirections4/status/1737830096126550386?ref_src=twsrc%5Etfw">2023年12月21日</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>シンプルなコードで、しかも-march=nativeを付けた場合のみ壊れる、ということで非常に力がある。自分もこれを機にmarchについて調べてしまった。</p> <h2 id="そもそもなぜ上記のコードは壊れているのか">そもそもなぜ上記のコードは壊れているのか?</h2> <p>元のコードからC++要素を取り除くと次のようになる。もちろんこのコードも壊れていることがコードテストから確認できる。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant">&lt;cstdio&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;cstring&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;cassert&gt;</span> <span class="synType">int</span> <span class="synIdentifier">main</span>() { <span class="synType">long</span> <span class="synType">long</span> a[<span class="synConstant">4</span>] = {<span class="synConstant">1</span>, <span class="synConstant">1</span>, <span class="synConstant">1</span>, <span class="synConstant">0</span>}, b[<span class="synConstant">4</span>]; <span class="synIdentifier">memmove</span>(b, a, <span class="synConstant">4</span> * <span class="synStatement">sizeof</span>(<span class="synType">long</span> <span class="synType">long</span>)); <span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i &lt; <span class="synConstant">4</span>; i++) { <span class="synIdentifier">printf</span>(<span class="synConstant">&quot;</span><span class="synSpecial">%lld</span><span class="synConstant"> &quot;</span>, b[i]); } <span class="synIdentifier">printf</span>(<span class="synConstant">&quot;</span><span class="synSpecial">\n</span><span class="synConstant">&quot;</span>); <span class="synStatement">return</span> <span class="synConstant">0</span>; } </pre> <p>実際にアセンブリを確認すると (<a href="https://godbolt.org/z/W9rf95znM">godbolt</a>)、<code>vpbroadcastq</code> および <code>vmovdqa</code> で <code>b</code> を <code>{1, 1, 1, 1}</code>で上書きした後 <code>b[3]</code> に対して何もしていないことがわかる。</p> <p>なお、<code>-march=native</code>が実際どう変換されているかは<code>gcc -### -march=native /usr/include/stdlib.h</code>で確認できる。AtCoderだと<code>-march=icelake-server</code>。</p> <p>これと <code>-march=native</code> を付けた場合のみ壊れるという現象から、GCCのAVX512周りになにかバグがあるのだろうと検討が付く。実際にGCCのissue trackerを眺めると、どうやら今回のバグはこれっぽい <a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108599">108599 &ndash; [12 Regression] Incorrect code generation newer intel architectures</a> 。12.3で修正されているので、AtCoderのGCCがアップデートされればこの問題は解決されそう(いつだろう)。</p> <h2 id="-marchnative--mtunenativeってそもそも何">-march=native -mtune=nativeってそもそも何?</h2> <p>とても雑に言うと</p> <ul> <li>-march=native: コンパイルしたパソコン(のCPU)専用のa.outを作ってくれという命令。生成されたa.outを他のパソコンにコピーすると、動くかもしれないし動かないかもしれない。</li> <li>-mtune=native: コンパイルしたパソコン(のCPU)向けのa.outを作ってくれという命令。生成されたa.outを他のパソコンにコピーすると、動くけどちょっと遅いかもしれない。</li> </ul> <p>という認識。例えば上記の<code>vpbroadcastq</code>命令が動くパソコンは限られるため、<code>-march=native</code>を付けないと使用されない。</p> <p>なお、<a href="https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html">x86 Options (Using the GNU Compiler Collection (GCC))</a> にあるように、<code>-march=native</code>は<code>-mtune=native</code>を含むため、両方指定する必要はない。</p> <blockquote><p>Specifying -march=cpu-type implies -mtune=cpu-type, except where noted otherwise.</p></blockquote> <h2 id="-marchnativeって効果あるの">-march=nativeって効果あるの?</h2> <p>GCC12からは次の理由で格段に効果が上がっている(GCC11までは-O3と組み合わせないと効果が薄い)。</p> <ul> <li><code>-march=native</code>で解禁される命令の大半は自動ベクトル化 (自動ベクトル化について: <a href="https://yukicoder.me/wiki/auto_vectorization">gcc &#x3067;&#x306E;&#x81EA;&#x52D5;&#x30D9;&#x30AF;&#x30C8;&#x30EB;&#x5316; Wiki - yukicoder</a> )向けの命令である。</li> <li>GCC11までは<code>-O3</code>を指定しないと自動ベクトル化がonにならなかったが、GCC12からは<code>-O2</code>でonになる。ただし<code>-O3</code>より自動ベクトル化のしきい値が高い(<code>fvect-cost-model=cheap</code>)。</li> </ul> <p>おそらく最も効果があるものの一つはbitsetのand/or/xor/count/any/all等なので、 <a href="https://atcoder.jp/contests/abc329/tasks/abc329_f">ABC 329 F</a> で不正を試みる。自明な $O(NQ / w)$ 解をMLE対策に少し工夫して投げると、次のように</p> <ul> <li>(C++20, -march=native): 1352ms / 4s AC <a href="https://atcoder.jp/contests/abc329/submissions/48871083">https://atcoder.jp/contests/abc329/submissions/48871083</a></li> <li>(C++17, -march=nativeなし): TLE(時々AC) <a href="https://atcoder.jp/contests/abc329/submissions/48871094">https://atcoder.jp/contests/abc329/submissions/48871094</a></li> </ul> <p>約3倍の高速化が確認できる。ただし、C++17でもpragmaをモリモリと付けるとACする</p> <ul> <li>(C++17, pragmaモリモリ): 1767ms / 4s AC <a href="https://atcoder.jp/contests/abc329/submissions/48871873">https://atcoder.jp/contests/abc329/submissions/48871873</a></li> </ul> <p>さらに <code>GCC optimize("Ofast")</code>を付けると、C++20より速くなる。</p> <ul> <li>(C++17, pragma, Ofast) 971ms <a href="https://atcoder.jp/contests/abc329/submissions/48871954">https://atcoder.jp/contests/abc329/submissions/48871954</a></li> </ul> <p>なんか実行時間がめちゃくちゃブレる(インスタンスガチャ?)ので、ブレの範疇な気もする まったく同じコードを2回投げて 1359ms vs 1907ms とか出た ( <a href="https://atcoder.jp/contests/abc329/submissions/48871947">https://atcoder.jp/contests/abc329/submissions/48871947</a>, <a href="https://atcoder.jp/contests/abc329/submissions/48871969">https://atcoder.jp/contests/abc329/submissions/48871969</a> )</p> <p>(不正以外で)まともに効果がありそうなのは DP 系だろうか、modintをMontgomery乗算で実装するとSIMD(自動ベクトル化)と相性がいいという小ネタもあり、云々</p> <h2 id="-marchnativeでpragmaって代替きかないの">-march=nativeでpragmaって代替きかないの?</h2> <p>大体聞きそうな気はする、懸念点は</p> <ul> <li>こだわるとジャッジごとに異なるpragmaを用意する必要がありそうで、ダルい</li> <li><code>-march=...</code>とpragmaが本当に等価なのかわかってない(例えばyukicoderの記事には <code>#pragma GCC optimize ("O3") は -O3 でのコンパイルとは異なるようです</code> とある)</li> <li>GCC13.2だとなんかpragma使えないかも? <a href="https://twitter.com/yosupot/status/1730356100363645093">https://twitter.com/yosupot/status/1730356100363645093</a></li> </ul> <p>あたりだろうか</p> <h2 id="結局--marchnative-って危険なの">結局 -march=native って危険なの?</h2> <p>もちろんまともな根拠はなくただの直観になるが、「わずかに危険だけど、問題になることはほぼない」程度ではないかと思っている</p> <ul> <li><a href="https://wiki.gentoo.org/wiki/Safe_CFLAGS">Safe CFLAGS - Gentoo wiki</a> Gentoo wiki曰く、<code>-march=native</code>はおススメである</li> </ul> <blockquote><p>A recommended default choice for CFLAGS or CXXFLAGS is to use -march=native</p></blockquote> <ul> <li><p>また、march=nativeのように"コンパイルしたパソコン専用に最適化"という概念自体もかなり使われているはず。確かrustだと<code>cargo install</code>でデフォルトで<code>-march=native</code>相当のオプションがonになったはず</p></li> <li><p>そもそもGCCのバグに出会うのがレアイベント</p> <ul> <li>自分は-march=nativeの有無でどうこうというのは初めて出会った気がする、記憶力がないだけか?</li> <li>AtCoderにGCC12と-march=nativeが導入される以前はノーカンという話もある。</li> </ul> </li> <li><p>一方で、pragmaで大体何とかなりそうだしわざわざ入れる必要がないのではという意見もありそう</p></li> </ul> <p>皆さんの意見はどうでしょうか(ブン投げ)</p> yosupo FHC 2023 Final 反省会会場 / 並列化研究 hatenablog://entry/6801883189067457168 2023-12-17T06:13:43+09:00 2023-12-17T06:13:43+09:00 概要 / 言い訳タイム FHC 2023 Finalの順位表を見ると、私がBをダウンロードだけして提出していないことがわかると思います。 そもそもカクタス(F)をシバけないとどうしようもないセットではあったのですが、それでもBを出していれば一応5位で入賞であり、このムーブは奇妙です。 これ 実際何が起きたのかというと、普通にTLEしました。AC率からもわかるように最悪ケースを作るのが難しい問題ではないのですが、$N \times M \le 1{,}000{,}000$に対して $N = M = 300$がほぼ最悪ケースだと勘違いしました。このコンテストは世界top25のコンテストです。 反省… <h1 id="概要--言い訳タイム">概要 / 言い訳タイム</h1> <p>FHC 2023 Finalの順位表を見ると、私がBをダウンロードだけして提出していないことがわかると思います。 そもそもカクタス(F)をシバけないとどうしようもないセットではあったのですが、それでもBを出していれば一応5位で入賞であり、このムーブは奇妙です。</p> <p><figure class="figure-image figure-image-fotolife" title="これ"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20231217/20231217043733.png" width="92" height="1200" loading="lazy" title="" class="hatena-fotolife hatena-fotolife-height-only" style="height:500px" itemprop="image"></span><figcaption>これ</figcaption></figure></p> <p>実際何が起きたのかというと、普通にTLEしました。AC率からもわかるように最悪ケースを作るのが難しい問題ではないのですが、$N \times M \le 1{,}000{,}000$に対して $N = M = 300$がほぼ最悪ケースだと勘違いしました。このコンテストは世界top25のコンテストです。</p> <h1 id="反省">反省</h1> <p>そもそもこのミスはリカバリー可能なはずでした。FHCは手元実行なので、適当にケースごとに並列化すれば容易に高速化できるはずです。この準備はしようしようと思っていたのですが、面倒でやらなかったらやられてしまいました。</p> <p>というわけで、この記事はFHC用に並列化環境を整備する話になります。</p> <h1 id="成果物">成果物</h1> <p>成果物をFHC 2023 Qual A1問題に適応したのがこちらになります。</p> <p><a href="https://gist.github.com/yosupo06/2c97d2f6188ddbdf21515592c5e45ada">qual-A1.cpp &middot; GitHub</a></p> <p>非ライブラリ部分は次のようになります。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synStatement">using</span> <span class="synType">namespace</span> <span class="synConstant">std</span>; <span class="synType">void</span> <span class="synIdentifier">solve</span>(<span class="synType">auto</span> input_end, <span class="synType">auto</span> output) { <span class="synType">int</span> s, d, k; <span class="synIdentifier">cin</span> &gt;&gt; s &gt;&gt; d &gt;&gt; k; <span class="synIdentifier">input_end</span>(); <span class="synType">int</span> buns = <span class="synConstant">2</span> * (s + d); <span class="synType">int</span> patties = s + <span class="synConstant">2</span> * d; <span class="synIdentifier">output</span>([&amp;] { <span class="synStatement">if</span> (buns &lt; k + <span class="synConstant">1</span> || patties &lt; k) { <span class="synIdentifier">cout</span> &lt;&lt; <span class="synConstant">&quot;NO&quot;</span> &lt;&lt; <span class="synIdentifier">endl</span>; } <span class="synStatement">else</span> { <span class="synIdentifier">cout</span> &lt;&lt; <span class="synConstant">&quot;YES&quot;</span> &lt;&lt; <span class="synIdentifier">endl</span>; } <span class="synIdentifier">cout</span> &lt;&lt; <span class="synIdentifier">flush</span>; }); }; <span class="synType">int</span> <span class="synIdentifier">main</span>() { <span class="synType">int</span> t; <span class="synIdentifier">cin</span> &gt;&gt; t; <span class="synIdentifier">fhc_solve</span>([&amp;](<span class="synType">auto</span> i, <span class="synType">auto</span> o){ <span class="synIdentifier">solve</span>(i, o); }, t); <span class="synStatement">return</span> <span class="synConstant">0</span>; } </pre> <p>可能な限り、「いつものように普通にsolve関数を書いたら、勝手に並列化される」に近いものを目指しました。</p> <ul> <li><code>input_end()</code>を入力が終わった後に呼ばないといけない</li> <li><code>output()</code>にラムダを渡して、そこですべての出力を行わないといけない</li> <li><code>output</code>の最後で必ず<code>flush</code>しないといけない</li> <li>multi-threadを使っているので、グローバル変数を書き換えたりすると、何が起きるかわからない</li> <li>実行のたびに出力をファイルに全部保存して消してないので、出力が大きいとやばそう</li> </ul> <p>などが残った制約です。代償としてライブラリ側は<code>freopen</code>などを乱用したコードになりました。</p> <p>実行すると次のようになります</p> <pre class="code" data-lang="" data-unlink>$ ./A1/main &lt; A1/big.in &gt; A1/big.out Start FHC solver: tmp = &#34;/tmp/output-14960984700273349145&#34;, parallel = 12 [#0] Start case: 1 / 79 [#0] End case: 1 (0 ms) [#1] Start case: 2 / 79 [#1] End case: 2 (0 ms) [#0] Start case: 3 / 79 [#0] End case: 3 (0 ms) [#2] Start case: 4 / 79 [#2] End case: 4 (0 ms) [#3] Start case: 5 / 79 [#10] Start case: 6 / 79 [#3] End case: 5 (0 ms) [#1] Start case: 7 / 79 [#10] End case: 6 (0 ms) [#5] Start case: 8 / 79 [#1] End case: 7 (0 ms) :</pre> <p>私のPCは(論理)12コアなので、12並列でケースが実行されます。</p> <h2 id="Bに再挑戦">Bに再挑戦</h2> <p>実際にBに再挑戦してみました、80s -> 28sなので、おおよそ3倍弱の高速化のようです。80sってそもそも間に合ってね?については、コンテスト中の6分間であわてて定数倍高速化した後のコードだからです(本当は一番最初のコードで試したかったのですが、上書きしていたため入手できませんでした…)。</p> <pre class="code" data-lang="" data-unlink>旧 ./B/main_single &lt; B/test.in &gt; B/test3.out 80.11s user 0.03s system 99% cpu 1:20.14 total 新 ./B/main &lt; B/test.in &gt; B/test3.out 148.96s user 0.15s system 527% cpu 28.245 total</pre> <p>また、ログは次のような感じです。最大ケースのCase 31(N = M = 1000)に引っ張られていることがわかります。</p> <pre class="code" data-lang="" data-unlink>: [#4] End case: 98 (13 ms) [#4] Start case: 99 / 100 [#4] End case: 99 (5 ms) [#4] Start case: 100 / 100 [#4] End case: 100 (14 ms) [#8] End case: 45 (1874 ms) [#7] End case: 37 (11847 ms) [#3] End case: 38 (11849 ms) [#2] End case: 36 (11859 ms) [#0] End case: 39 (11978 ms) [#10] End case: 35 (12436 ms) [#6] End case: 41 (12013 ms) [#9] End case: 40 (12544 ms) [#11] End case: 42 (12237 ms) [#5] End case: 43 (11407 ms) [#1] End case: 31 (28236 ms) ./B/main &lt; B/test.in &gt; B/test3.out 148.96s user 0.15s system 527% cpu 28.245 total</pre> <h1 id="Eにも挑戦">Eにも挑戦</h1> <p>また、結構実行時間がやばかったEに対してもやってみたところ… MLEしました。</p> <p>並列なしでメモリを4GBぐらい使うとんでもプログラムなので、12並列ならばさもありなんです。</p> <p>3並列ならば、高速化が確認できました(155s -> 70s)。しかし、いざ本番でMLEで突然死、は困るので、この弱点の対応法は悩みどころです。<s>48GB余裕なぐらいメモリ増設すればいいだけでは?</s> お安い対策としては、そもそもクラウドに巨大インタンスを借りてそこでやるなどが考えられます。ルール的にOKなのかは微妙ですが…</p> <pre class="code" data-lang="" data-unlink>./E/main_single &lt; ./E/big.in &gt; ./E/big2.out 139.51s user 15.66s system 99% cpu 2:35.24 total ./E/main &lt; ./E/big.in &gt; ./E/big2.out 183.57s user 5.30s system 269% cpu 1:10.12 total</pre> <p>ログを確認したところ、一番時間のかかるケースは10s程度だったので、クラウド等にガチ強力インスタンス借りれば10s切れそうではある</p> <pre class="code" data-lang="" data-unlink>[#0] End case: 15 (10019 ms)</pre> yosupo 遅延Segtree3 hatenablog://entry/6801883189065231293 2023-12-09T01:06:58+09:00 2023-12-09T01:08:03+09:00 大嘘昔話 実は「segtreeというのはモノイドを載せられて、lazysegtreeはそれにいい感じの作用が行えて…」のようにsegtreeが抽象化されたのは割と最近です。 昔はなんかsegtreeって大体実装一緒だな…と思いながら、みな自分のstarryskytree.cppを毎回コピペして適当に書き直して使っていました。 もちろん適切にクラス等を使って最強のsegtreeを作れば勝ちまくりモテまくりであることには皆薄々気づいており、私もそのような夢を追い求める若者の一人でした。 その名残がこちらです。 遅延Segtree2 - よすぽの日記 遅延SegTree - よすぽの日記 2023年… <h2 id="大嘘昔話">大嘘昔話</h2> <p>実は「segtreeというのはモノイドを載せられて、lazysegtreeはそれにいい感じの作用が行えて…」のようにsegtreeが抽象化されたのは割と最近です。 昔はなんかsegtreeって大体実装一緒だな…と思いながら、みな自分のstarryskytree.cppを毎回コピペして適当に書き直して使っていました。</p> <p>もちろん適切にクラス等を使って最強のsegtreeを作れば勝ちまくりモテまくりであることには皆薄々気づいており、私もそのような夢を追い求める若者の一人でした。 その名残がこちらです。</p> <ul> <li><a href="https://yosupo.hatenablog.com/entry/2018/08/11/015634">&#x9045;&#x5EF6;Segtree2 - &#x3088;&#x3059;&#x307D;&#x306E;&#x65E5;&#x8A18;</a></li> <li><a href="https://yosupo.hatenablog.com/entry/2018/08/11/005239">&#x9045;&#x5EF6;SegTree - &#x3088;&#x3059;&#x307D;&#x306E;&#x65E5;&#x8A18;</a></li> </ul> <h1 id="2023年">2023年</h1> <p>時は2023年、昔はC++11の機能は新しいといわれていましたが、今ではC++20がどこのジャッジでも使えます(正確には使えないジャッジは引退しました)。C++20と言えばconcept、今回は昔を思いながら、conceptの勉強がてら抽象化segtreeに挑戦してみました。</p> <p>上の遅延SegTree / 遅延Segtree2にあるように、大体実装方法は</p> <ul> <li><code>struct</code>を自分で定義してそれをsegtreeに渡す</li> <li>lambda/関数 を演算の個数だけ用意して一気にsegtreeに渡す</li> </ul> <p>の2種類だと思うんですが、今回は両方できるようにしてみました。2018年ならいざ知らず同様の実装がそこら中にあると思う。</p> <h1 id="コード">コード</h1> <p>こちらです。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant">&lt;vector&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;iostream&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;numeric&gt;</span> <span class="synType">template</span> &lt;<span class="synType">class</span> T&gt; concept monoid = <span class="synIdentifier">requires </span>(T&amp; x, <span class="synType">typename</span> T::S s) { { x.<span class="synIdentifier">op</span>(s, s) } -&gt; <span class="synConstant">std</span>::<span class="synType">same_as</span>&lt;<span class="synType">typename</span> T::S&gt;; { x.<span class="synIdentifier">e</span>() } -&gt; <span class="synConstant">std</span>::<span class="synType">same_as</span>&lt;<span class="synType">typename</span> T::S&gt;; }; <span class="synType">template</span> &lt;monoid M&gt; <span class="synType">struct</span> SegTree { <span class="synStatement">using</span> S = M::S; M m; <span class="synConstant">std</span>::<span class="synType">vector</span>&lt;S&gt; v; <span class="synIdentifier">SegTree</span>(M _m, <span class="synConstant">std</span>::<span class="synType">vector</span>&lt;S&gt; _v) : <span class="synIdentifier">m</span>(_m), <span class="synIdentifier">v</span>(_v) { } S <span class="synIdentifier">all_prod</span>() { <span class="synComment">// </span><span class="synTodo">TODO</span><span class="synComment"> optimize :)</span> S val = m.<span class="synIdentifier">e</span>(); <span class="synStatement">for</span> (<span class="synType">auto</span> x : v) { val = m.<span class="synIdentifier">op</span>(val, x); } <span class="synStatement">return</span> val; } }; <span class="synType">template</span> &lt;<span class="synType">class</span> T, <span class="synType">class</span> OP, <span class="synType">class</span> E&gt; <span class="synType">struct</span> LambdaMonoid { <span class="synStatement">using</span> S = T; S <span class="synIdentifier">op</span>(S a, S b) { <span class="synStatement">return</span> <span class="synIdentifier">_op</span>(a, b); } S <span class="synIdentifier">e</span>() { <span class="synStatement">return</span> <span class="synIdentifier">_e</span>(); } <span class="synIdentifier">LambdaMonoid</span>(OP op, E e) : <span class="synIdentifier">_op</span>(op), <span class="synIdentifier">_e</span>(e) {} <span class="synStatement">private</span>: OP _op; E _e; }; <span class="synType">template</span> &lt;<span class="synType">class</span> OP, <span class="synType">class</span> E&gt; <span class="synIdentifier">LambdaMonoid</span>(OP op2, E e2)-&gt;LambdaMonoid&lt;<span class="synType">decltype</span>(<span class="synIdentifier">e2</span>()), OP, E&gt;; <span class="synComment">// --- ここまでライブラリ ---</span> <span class="synType">struct</span> AddInt { <span class="synStatement">using</span> S = <span class="synType">int</span>; S <span class="synIdentifier">op</span>(S a, S b) { <span class="synStatement">return</span> a + b; } S <span class="synIdentifier">e</span>() { <span class="synStatement">return</span> <span class="synIdentifier">S</span>(<span class="synConstant">0</span>); } }; <span class="synType">int</span> <span class="synIdentifier">main</span>() { <span class="synConstant">std</span>::<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; <span class="synIdentifier">v</span>(<span class="synConstant">10</span>); <span class="synConstant">std</span>::<span class="synIdentifier">iota</span>(v.<span class="synIdentifier">begin</span>(), v.<span class="synIdentifier">end</span>(), <span class="synConstant">0</span>); SegTree <span class="synIdentifier">seg0</span>(<span class="synIdentifier">AddInt</span>(), v); SegTree <span class="synIdentifier">seg1</span>( <span class="synIdentifier">LambdaMonoid</span>([&amp;](<span class="synType">int</span> a, <span class="synType">int</span> b) { <span class="synStatement">return</span> a + b; }, [&amp;]() { <span class="synStatement">return</span> <span class="synConstant">0</span>; }), v); <span class="synConstant">std</span>::<span class="synIdentifier">cout</span> &lt;&lt; <span class="synConstant">&quot;sum(1..10) = &quot;</span> &lt;&lt; seg0.<span class="synIdentifier">all_prod</span>() &lt;&lt; <span class="synConstant">std</span>::<span class="synIdentifier">endl</span>; <span class="synConstant">std</span>::<span class="synIdentifier">cout</span> &lt;&lt; <span class="synConstant">&quot;sum(1..10) = &quot;</span> &lt;&lt; seg1.<span class="synIdentifier">all_prod</span>() &lt;&lt; <span class="synConstant">std</span>::<span class="synIdentifier">endl</span>; <span class="synStatement">return</span> <span class="synConstant">0</span>; } </pre> <p>タイトルに遅延segtreeとありますが、シンプルに詐欺です。普通のsegtreeしか試してみていません。</p> <h2 id="解説">解説</h2> <p>まず、最初の数行がいきなり重要です</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">template</span> &lt;<span class="synType">class</span> T&gt; concept monoid = <span class="synIdentifier">requires </span>(T&amp; x, <span class="synType">typename</span> T::S s) { { x.<span class="synIdentifier">op</span>(s, s) } -&gt; <span class="synConstant">std</span>::<span class="synType">same_as</span>&lt;<span class="synType">typename</span> T::S&gt;; { x.<span class="synIdentifier">e</span>() } -&gt; <span class="synConstant">std</span>::<span class="synType">same_as</span>&lt;<span class="synType">typename</span> T::S&gt;; }; <span class="synType">template</span> &lt;monoid M&gt; <span class="synType">struct</span> SegTree { <span class="synStatement">using</span> S = M::S; M m; : </pre> <p>これは、<code>monoid</code> conceptを定義し、<code>struct SegTree</code>がこの<code>monoid</code> conceptを満たす<code>M</code>しか受け取れないようにしています。<code>M</code>が<code>monoid</code> conceptを満たすとは、</p> <ul> <li><code>using S = hoge</code> として値の型が定義されている</li> <li><code>op(S, S) -&gt; S</code>をメンバとして持つ</li> <li><code>e() -&gt; S</code>をメンバとして持つ</li> </ul> <p>という、大体atcoder libraryと同じ定義です。例えば下のほうにある<code>struct AddInt</code>が<code>monoid</code> conceptを満たします。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">struct</span> AddInt { <span class="synStatement">using</span> S = <span class="synType">int</span>; S <span class="synIdentifier">op</span>(S a, S b) { <span class="synStatement">return</span> a + b; } S <span class="synIdentifier">e</span>() { <span class="synStatement">return</span> <span class="synIdentifier">S</span>(<span class="synConstant">0</span>); } }; </pre> <p>なので、こういうAddInt構造体を用意して、<code>main</code>関数の最初で行われているように</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink> <span class="synConstant">std</span>::<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; <span class="synIdentifier">v</span>(<span class="synConstant">10</span>); <span class="synConstant">std</span>::<span class="synIdentifier">iota</span>(v.<span class="synIdentifier">begin</span>(), v.<span class="synIdentifier">end</span>(), <span class="synConstant">0</span>); SegTree <span class="synIdentifier">seg0</span>(<span class="synIdentifier">AddInt</span>(), v); </pre> <p>と書けば<code>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]</code>の格納されたsegtreeが出来ます。</p> <p>また、わざわざ<code>struct AddInt</code>のようにstructを定義しなくても、ラムダ式を書き並べるだけでsegtreeを使うこともできます。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink> SegTree <span class="synIdentifier">seg1</span>( <span class="synIdentifier">LambdaMonoid</span>([&amp;](<span class="synType">int</span> a, <span class="synType">int</span> b) { <span class="synStatement">return</span> a + b; }, [&amp;]() { <span class="synStatement">return</span> <span class="synConstant">0</span>; }), v); </pre> <p>これがどういう仕組みかというと、ラムダ式を受け取って<code>monoid</code> structのようにふるまう<code>LambdaMonoid</code>もライブラリ側に用意しておきました。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">template</span> &lt;<span class="synType">class</span> T, <span class="synType">class</span> OP, <span class="synType">class</span> E&gt; <span class="synType">struct</span> LambdaMonoid { <span class="synStatement">using</span> S = T; S <span class="synIdentifier">op</span>(S a, S b) { <span class="synStatement">return</span> <span class="synIdentifier">_op</span>(a, b); } S <span class="synIdentifier">e</span>() { <span class="synStatement">return</span> <span class="synIdentifier">_e</span>(); } <span class="synIdentifier">LambdaMonoid</span>(OP op, E e) : <span class="synIdentifier">_op</span>(op), <span class="synIdentifier">_e</span>(e) {} <span class="synStatement">private</span>: OP _op; E _e; }; <span class="synType">template</span> &lt;<span class="synType">class</span> OP, <span class="synType">class</span> E&gt; <span class="synIdentifier">LambdaMonoid</span>(OP op2, E e2)-&gt;LambdaMonoid&lt;<span class="synType">decltype</span>(<span class="synIdentifier">e2</span>()), OP, E&gt;; </pre> <p>応用編として、「<code>pair&lt;S, S&gt;</code>を使うことで一般のモノイドをReverse可能なモノイドに変換するやつ」とかが実現できると思っています。なんのこっちゃという話ですが、平衡二分木でこういうのが欲しくなります。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">template</span> &lt;monoid M&gt; <span class="synType">struct</span> AttachReverse { <span class="synStatement">using</span> S = <span class="synConstant">std</span>::<span class="synType">pair</span>&lt;M::S, M::S&gt;; S <span class="synIdentifier">op</span>(S a, S b) { <span class="synStatement">return</span> {m.<span class="synIdentifier">op</span>(a, b), m.<span class="synIdentifier">op</span>(b, a)}; } S <span class="synIdentifier">e</span>() { <span class="synStatement">return</span> {m.<span class="synIdentifier">e</span>(), m.<span class="synIdentifier">e</span>()}; } S <span class="synIdentifier">rev</span>(S a) { <span class="synStatement">return</span> {a.second, a.first}; } <span class="synIdentifier">AttachReverse</span>(M _m) : <span class="synIdentifier">m</span>(_m) {} <span class="synStatement">private</span>: M m; }; </pre> <h2 id="結論">結論</h2> <p>全てが懐古</p> yosupo Weighted balanced binary tree の平衡条件をWolfram Engineで hatenablog://entry/6801883189064952668 2023-12-07T23:16:54+09:00 2023-12-08T08:15:03+09:00 注: ガチ誰得記事 Weighted balanced binary treeという平衡二分探索木があります。詳細はwikipedia(Weight-balanced tree - Wikipedia)が詳しいのですが、ざっくり言うと 「子のサイズが親のサイズの少なくとも $\alpha$ 倍」 もしくは同値な表現として「左右の子のサイズが高々 $\frac{1}{\alpha} - 1$ 倍しか違わない」 という平衡条件の木です。ここで、サイズというのは葉にのみ値を載せる木ならば葉の個数、全部の頂点に値を載せる木ならば(頂点の個数) + 1とします。 $\alpha$ を大きくすればするほど… <p>注: ガチ誰得記事</p> <p>Weighted balanced binary treeという平衡二分探索木があります。詳細はwikipedia(<a href="https://en.wikipedia.org/wiki/Weight-balanced_tree">Weight-balanced tree - Wikipedia</a>)が詳しいのですが、ざっくり言うと</p> <ul> <li>「子のサイズが親のサイズの少なくとも $\alpha$ 倍」</li> <li>もしくは同値な表現として「左右の子のサイズが高々 $\frac{1}{\alpha} - 1$ 倍しか違わない」</li> </ul> <p>という平衡条件の木です。ここで、サイズというのは葉にのみ値を載せる木ならば葉の個数、全部の頂点に値を載せる木ならば(頂点の個数) + 1とします。</p> <p>$\alpha$ を大きくすればするほど回転が増える代わりに強く平衡(=木の高さが低くなる)し、$\alpha$ を小さくすればするほど回転が減る代わりに平衡が弱くなります。</p> <p>このサイズ情報はまず間違いなく競技プログラミングだと管理する(K番目のアクセスに必要なため)ので、赤黒木等と違い追加メモリが実質0と言う利点があります。カロリー0!</p> <p>この木は強そうなのですが、うまく動く証明が難しいです。葉にのみ値を載せる木の場合のmerge関数はたとえば次のようになります。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink>Node* <span class="synIdentifier">merge</span>(Node* l, Node* r) { <span class="synStatement">if</span> (<span class="synIdentifier">is_balanced</span>(l-&gt;size, r-&gt;size)) <span class="synStatement">return</span> <span class="synIdentifier">make_node</span>(l, r); <span class="synStatement">if</span> (l-&gt;size &gt; r-&gt;size) { <span class="synType">auto</span> x = l-&gt;l; <span class="synType">auto</span> y = <span class="synIdentifier">merge</span>(l-&gt;r, r); <span class="synStatement">if</span> (<span class="synIdentifier">is_balanced</span>(x-&gt;size, y-&gt;size)) <span class="synStatement">return</span> <span class="synIdentifier">make_node</span>(x, y); <span class="synStatement">if</span> (<span class="synIdentifier">is_balanced</span>(x-&gt;size, y-&gt;l-&gt;size) &amp;&amp; <span class="synIdentifier">is_balanced</span>(x-&gt;size + y-&gt;l-&gt;size, y-&gt;r-&gt;size)) <span class="synStatement">return</span> <span class="synIdentifier">make_node</span>(<span class="synIdentifier">make_node</span>(x, y-&gt;l), y-&gt;r); <span class="synStatement">return</span> <span class="synIdentifier">make_node</span>(<span class="synIdentifier">make_node</span>(x, y-&gt;l-&gt;l), <span class="synIdentifier">make_node</span>(y-&gt;l-&gt;r, y-&gt;r)); } <span class="synStatement">else</span> { ... } } </pre> <p>このコードはどういうことかというと、lとrをマージしたい時、</p> <ul> <li>lとrをそのままマージして問題ない: マージする</li> <li>lがrに対してデカすぎてマージできない場合は、l->rとrを再帰的にmergeする。そしてl->lをx、merge(l->r, r)をyとする。</li> <li>a = x, b = y->l->l, c = y->l->r, d = y->rとして、下図の3つの木のうち、少なくともどれか一つはうまくバランスしている。</li> </ul> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20231207/20231207223818.jpg" width="1200" height="750" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>という、大変魔法のようなマージ関数になります。</p> <p>もちろん、任意の $\alpha$ についてこのmerge関数が動くわけではなく、wikipediaにあるように $0 \lt \alpha \lt 1 - \frac{1}{\sqrt{2}}$ ならば動きます。</p> <p>今日はこの事実をWolfram Engine (<a href="https://www.wolfram.com/engine/index.php.ja?source=footer">Wolfram Engine</a>) を利用して確かめていきましょう。</p> <ul> <li>変数 <code>t</code> を $\frac{1}{\alpha} - 1$ とします、つまり左右の子は高々 <code>t</code> 倍しかサイズが違わないという意味です。</li> <li>変数 <code>a, b, c, d</code> を上図の <code>a, b, c, d</code> のサイズとします</li> <li>変数 <code>e</code> を <code>r</code> のサイズとします。つまり <code>l</code> のサイズは <code>a + b + c + d - e</code> です。</li> </ul> <p>まず、次のことが言えます</p> <ul> <li><code>l</code>は <code>r</code> より <code>t</code> 倍以上大きい: <code>(a+b+c+d-e) &gt; t e</code></li> <li>元々 <code>l-&gt;l</code>と<code>l-&gt;r</code>はバランスしていた: <code>a &lt;= t(b+c+d-e) &amp;&amp; t a &gt;= (b+c+d-e)</code></li> <li><code>b</code>と<code>c</code>はバランスしている: <code>b &lt;= t c &amp;&amp; t b &gt;= c</code></li> <li><code>b + c</code>と<code>d</code>はバランスしている: <code>(b+c) &lt;= t d &amp;&amp; t(b+c) &gt;= d</code></li> </ul> <p>そしてこの条件下で</p> <ul> <li>図の一番上のマージが失敗する = (<code>a</code>と<code>b+c+d</code>がバランスしていない): <code>(t a &lt; b+c+d || a &gt; t(b+c+d))</code></li> <li>二番目のマージが失敗する = (<code>a</code>と<code>b+c</code>がバランスしていない OR <code>a+b+c</code>と<code>d</code>がバランスしていない): <code>(t a &lt; b+c || a &gt; t(b+c) || t(a+b+c) &lt; d || a+b+c &gt; t d)</code></li> <li>三番目のマージが失敗する = (<code>a</code>と<code>b</code> / <code>c</code>と<code>d</code> / <code>a+b</code> と <code>c+d</code> のどれかがバランスしていない): <code>(t a &lt; b || a &gt; t b || t(a+b) &lt; c+d || a+b &gt; t(c+d) || t c &lt; d || c &gt; t d )</code></li> </ul> <p>これを全てまとめてwolfram engineに投げると次のようになります</p> <pre class="code" data-lang="" data-unlink>In[1]:= Resolve[Exists[{a, b, c, d, e}, 1 &lt;= t &amp;&amp; 0 &lt;= a &amp;&amp; 0 &lt;= b &amp;&amp; 0 &lt;= c &amp;&amp; 0 &lt;= d &amp;&amp; 0 &lt;= e &amp;&amp; (a+b+c+d-e) &gt; t e &amp;&amp; a &lt;= t(b+c+d-e) &amp;&amp; t a &gt;= (b+c+d-e ) &amp;&amp; b &lt;= t c &amp;&amp; t b &gt;= c &amp;&amp; (b+c) &lt;= t d &amp;&amp; t(b+c) &gt;= d &amp;&amp; (t a &lt; b+c+d || a &gt; t(b+c+d)) &amp;&amp; (t a &lt; b+c || a &gt; t(b+c) || t(a+b+c) &lt; d || a+b+c &gt; t d) &amp;&amp; (t a &lt; b || a &gt; t b || t(a+b) &lt; c+d || a+b &gt; t(c+d) || t c &lt; d || c &gt; t d )], t] Out[1]= 1 &lt;= t &lt; 1 + Sqrt[2]</pre> <p>というわけで、$1 + \sqrt{2} \le t$、つまり$\alpha \le \frac{1}{2 + \sqrt{2}} = 1 - \frac{1}{\sqrt{2}}$ならば正しく動くことがわかりました。</p> <p>結論: wolfram engineってすごい</p> yosupo データ構造をマージする一般的なテクを高速化するやや一般的なテク hatenablog://entry/6801883189062762664 2023-11-30T01:38:08+09:00 2023-11-30T01:38:08+09:00 知る人ぞ知る謎の知識みたいになってる気がしたのでメモ。 サイズ $A$ とサイズ $B$ のデータ構造 ($A < B$) が $O(A \log B)$ 時間でマージできる場合、合計 $O(N \log ^2 N)$ 時間で $N$ 個の要素がマージできます。競プロだと std::set や std::priority_queue などです。 ここで、もしもマージが $O(A (\log B - \log A + 1))$ に高速化できたならば、合計の計算量が $O(N \log N)$ になります。証明は普通のデータ構造をマージする一般的なテクとほぼ同じで、ある要素を含むデータ構造のサイズ… <p>知る人ぞ知る謎の知識みたいになってる気がしたのでメモ。</p> <p>サイズ $A$ とサイズ $B$ のデータ構造 ($A &lt; B$) が $O(A \log B)$ 時間でマージできる場合、合計 $O(N \log ^2 N)$ 時間で $N$ 個の要素がマージできます。競プロだと <code>std::set</code> や <code>std::priority_queue</code> などです。</p> <p>ここで、もしもマージが $O(A (\log B - \log A + 1))$ に高速化できたならば、合計の計算量が $O(N \log N)$ になります。証明は普通のデータ構造をマージする一般的なテクとほぼ同じで、ある要素を含むデータ構造のサイズが $1 \to x \to y \to N$ と成長したなら、この要素にかかる計算量は $(\log x - \log 1 + 1) + (\log y - \log x + 1) + (\log N - \log z + 1) = O(\log N)$ 、といったノリです。</p> <p>でも$O(A (\log B - \log A + 1))$ なんて謎の計算量なんて…と思うかもしれませんが、実はかなり多くのデータ構造がこの計算量を達成できます。すごいですね。</p> <p><span style="color: #ff5252">平衡二分木の merge / split が登場してややこしいので、データ構造をマージする操作を <code>meld</code> と呼ぶことにします</span></p> <h2 id="二分ヒープ">二分ヒープ</h2> <p><code>std::priority_queue</code> の中身です。(自分で <code>std::priority_queue</code> 相当のものを実装する必要がありますが)実はpriority_queue + マージテクは $\log$ 一個です。でも $O(N \log ^2 N)$ のマージがそもそも爆速だからうまみが少なそう。</p> <p>まず、二分ヒープを線形時間で構築するアルゴリズムを理解する必要があります。たとえば下のコードのようになります。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> <span class="synIdentifier">down_heap</span>(<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&amp; d, <span class="synType">int</span> u) { <span class="synType">int</span> n = <span class="synType">int</span>(d.<span class="synIdentifier">size</span>()); <span class="synStatement">while</span> (<span class="synConstant">2</span> * u + <span class="synConstant">1</span> &lt; n) { <span class="synType">int</span> v = (<span class="synConstant">2</span> * u + <span class="synConstant">1</span>); <span class="synStatement">if</span> (v + <span class="synConstant">1</span> &lt; n &amp;&amp; h.d[v] &lt; h.d[v + <span class="synConstant">1</span>]) v++; <span class="synStatement">if</span> (h.d[u] &gt;= h.d[v]) <span class="synStatement">break</span>; <span class="synIdentifier">swap</span>(h.d[u], h.d[v]); u = v; } } <span class="synType">void</span> <span class="synIdentifier">build_heap</span>(<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&amp; d) { <span class="synType">int</span> n = <span class="synType">int</span>(d.<span class="synIdentifier">size</span>()); <span class="synStatement">for</span> (<span class="synType">int</span> i = n - <span class="synConstant">1</span>; i &gt;= <span class="synConstant">0</span>; i--) { <span class="synIdentifier">down_heap</span>(d, i); } <span class="synStatement">return</span> h; } </pre> <p>「ある頂点について、葉の方向に自分より大きい要素とswapしていく」<code>down_heap</code>関数を、葉から順に全ての頂点に呼ぶと二分ヒープが構築できます。計算量は各頂点の「葉までの距離」の総和になり、式をコネコネすると線形時間であることがわかります。</p> <p>meld関数のコードは次のようになります。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">void</span> <span class="synIdentifier">meld</span>(<span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&amp; h, <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt;&amp; other) { <span class="synStatement">if</span> (h.<span class="synIdentifier">len</span>() &lt; other.<span class="synIdentifier">len</span>()) <span class="synIdentifier">swap</span>(h, other); <span class="synStatement">if</span> (other.<span class="synIdentifier">empty</span>()) <span class="synStatement">return</span>; <span class="synType">int</span> l = <span class="synType">int</span>(h.<span class="synIdentifier">size</span>()), r = l + <span class="synType">int</span>(other.<span class="synIdentifier">size</span>()) - <span class="synConstant">1</span>; h.<span class="synIdentifier">insert</span>(h.<span class="synIdentifier">end</span>(), other.<span class="synIdentifier">begin</span>(), other.<span class="synIdentifier">end</span>()); <span class="synStatement">while</span> (l) { l = (l - <span class="synConstant">1</span>) / <span class="synConstant">2</span>; r = (r - <span class="synConstant">1</span>) / <span class="synConstant">2</span>; <span class="synStatement">for</span> (<span class="synType">int</span> i = r; i &gt;= l; i--) { <span class="synIdentifier">down_heap</span>(h, i); } } } </pre> <p>これは何をしているのかというと、小さいほうのヒープの要素を適当に大きいヒープの末尾に追加した後、「新しく追加した要素を子孫に持つ要素」全てに対して先述の<code>down_heap</code>関数を呼んでいるだけです。 計算量ですが、各頂点についてmin(<code>other.size()</code>, 葉までの距離)であること、そしてこれの総和が上記の $O(A (\log B - \log A + 1))$ になることが示せます。</p> <h2 id="stdset-by-merge-split-baseの平衡二分木">std::set (by merge-split baseの平衡二分木)</h2> <p>std::setも、自作の平衡二分木で実装することで $O(A (\log B - \log A + 1))$が達成できます。まず、一般に merge-split base の平衡二分木ならなんでも可能な(ハズの)方法を紹介しますが、おそらく定数倍はとても悪いです。</p> <p>$A \le \sqrt{B}$ の場合は愚直に $A$ 回 insertすればよいので、$\sqrt{B} &lt; A$ としてよい。</p> <ul> <li>サイズ $B$ の木を サイズ $B / A$ (ぐらい)の木に $A$ 分割する。 <ul> <li>愚直にsplitを使うと $O(A \log B)$ だが、例えば8分割したいなら (2分割 => それぞれを2分割 => それぞれを2分割)、のように、なるべくsplitする木のサイズが小さくなるように切っていくと $O(A (\log B - \log A + 1))$ になる</li> </ul> </li> <li>分割した木それぞれに対して、サイズ $A$ の木の(対応する値の範囲の)要素を追加していく <ul> <li>追加する要素が $B / A$ 個以下なら愚直にinsert</li> <li>追加する要素が $B / A$ 個以上なら(両方の木をvectorにする) => (std::merge) => (vectorを長さ $B / A$ のブロックに分割する) => (それぞれのブロックから、線形時間で新しい木を構築する)で、線形時間でマージする</li> </ul> </li> <li>分割した木たちを(splitと同様に、いい感じに)マージして新しい木を作る。 <ul> <li>サイズが $(B / A)$以上$2(B / A)$以下の木たちのマージになり、splitと同様の計算量解析が可能</li> </ul> </li> </ul> <p>で$O(A (\log B - \log A + 1))$ になっている…ハズ</p> <h2 id="stdset-by-merge-split-baseの多くの平衡二分木">std::set (by merge-split baseの多くの平衡二分木)</h2> <p>葉にだけ値を持たせる赤黒木など、サイズ $L, R (L &lt; R)$の木のmerge (not meld)が $O(\log R - \log L + 1)$であることが保証可能な平衡二分木は割と簡単に実装できます。(参考: <a href="https://www2.ioi-jp.org/camp/2012/2012-sp-tasks/2012-sp-day4-copypaste-slides.pdf">https://www2.ioi-jp.org/camp/2012/2012-sp-tasks/2012-sp-day4-copypaste-slides.pdf</a>) このような平衡二分木なら、次のような割と素朴なmeld関数が上手くいくはずです。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink>Node* <span class="synIdentifier">meld</span>(Node* n, <span class="synType">deque</span>&lt;<span class="synType">int</span>&gt;&amp; q) { <span class="synStatement">while</span>(q.<span class="synIdentifier">size</span>() &amp;&amp; q.<span class="synIdentifier">front</span>() == n-&gt;val) q.<span class="synIdentifier">pop_front</span>(); <span class="synComment">// multisetにしたい場合はこの行を消す</span> <span class="synStatement">if</span> (n-&gt;val &lt; q.<span class="synIdentifier">front</span>()) <span class="synStatement">return</span> n; <span class="synStatement">if</span> (<span class="synIdentifier">is_leaf</span>(n)) { <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; v; <span class="synStatement">while</span> (q.<span class="synIdentifier">size</span>() &amp;&amp; q.<span class="synIdentifier">front</span>() &lt; n-&gt;val) { v.<span class="synIdentifier">push_back</span>(q.<span class="synIdentifier">front</span>()); q.<span class="synIdentifier">pop_front</span>(); } <span class="synStatement">return</span> <span class="synIdentifier">merge</span>(<span class="synIdentifier">build_tree</span>(v), n); <span class="synComment">// build_tree(v): |v|時間で木を構築する関数</span> } Node* l = <span class="synIdentifier">meld</span>(n-&gt;l, q); Node* r = <span class="synIdentifier">meld</span>(n-&gt;r, q); <span class="synStatement">return</span> <span class="synIdentifier">merge</span>(l, r); <span class="synComment">// (1)</span> } Node* <span class="synIdentifier">meld</span>(Node* n, Node* m) { <span class="synStatement">if</span> (n-&gt;size &lt; m-&gt;size) <span class="synIdentifier">swap</span>(n, m); <span class="synType">deque</span>&lt;<span class="synType">int</span>&gt; q = <span class="synIdentifier">to_deque</span>(m); <span class="synComment">// to_deque(m): mの要素を舐めて(sortedな)dequeを線形時間で生成する関数</span> n = <span class="synIdentifier">meld</span>(n, q); <span class="synStatement">return</span> <span class="synIdentifier">merge</span>(n, <span class="synIdentifier">build_tree</span>(q)); } </pre> <p>まず、meld関数が呼ばれる回数は、<code>n-&gt;size</code>が $B / A$以上かどうかで場合分けすれば示せます。</p> <ul> <li><code>n-&gt;size</code> が $B / A$ 以上 : そもそもこういう頂点は $O(A)$ 個しかない</li> <li><code>n-&gt;size</code> が $B / A$ 未満: 高さが $O(\log (B / A)) = O(\log B - \log A + 1)$ なので合計 $O(A (\log B - \log A + 1))$</li> </ul> <p>コード中の(1)の部分の計算量が本質です。ここで、merge関数の計算量により、meld前後で木のサイズが $k$ 倍に増えていたら (1) の部分の計算量が $O(\log k)$ であることを利用します。</p> <ul> <li>木のサイズが $2$ 倍以上に増えるような頂点数は?: 高々 $O(A)$</li> <li>木のサイズが $4$ 倍以上に増えるような頂点数は?: 高々 $O(A / 2)$</li> <li>:</li> </ul> <p>より、(*)の部分の計算量の合計は(meldが呼ばれた回数に加えて)高々 $O(A)$ です。</p> <h2 id="stdset-by-splay-tree">std::set (by splay-tree)</h2> <p>は愚直に小さいほうの集合を昇順(or 降順)にinsertしていくだけで $O(A (\log B - \log A + 1)$ って噂を聞いたんですが、本当かわかりません。</p> <h2 id="stdset-by-treap">std::set (by treap)</h2> <p>は割と容易に $O(A (\log B - \log A + 1)$ のmeldが書けるって聞きました。いかがでしたか</p> yosupo UCUP 2-11 (Nanjing) E: Extending Distance / 最小費用流の双対 hatenablog://entry/6801883189062229434 2023-11-27T23:08:16+09:00 2023-11-27T23:08:16+09:00 久しぶりですごい時間がかかったのでメモ 大体次の問題。 $N$ 頂点 $M$ 辺の有向グラフと非負整数 $K$ が与えられる。各辺には非負整数の重みが付いていて、辺 $e$ の重みを $d_e$ とする。$K$ は $1 \to N$ の最短距離より小さくない。 $1$ 円払うと好きな辺の重みを $1$ 増やせる時、頂点 $1$ から $N$ の最短距離を $K$ にするために必要な最小コストは? 構築あり: 各辺について何回伸ばすかも復元する必要あり まず、この問題はほぼ最小費用循環流の双対問題そのものなので、構築がなければ難しくない。というか大体コレ J - Longest Shortes… <p>久しぶりですごい時間がかかったのでメモ</p> <p>大体次の問題。</p> <ul> <li>$N$ 頂点 $M$ 辺の有向グラフと非負整数 $K$ が与えられる。各辺には非負整数の重みが付いていて、辺 $e$ の重みを $d_e$ とする。$K$ は $1 \to N$ の最短距離より小さくない。 $1$ 円払うと好きな辺の重みを $1$ 増やせる時、頂点 $1$ から $N$ の最短距離を $K$ にするために必要な最小コストは? <strong>構築あり</strong>: 各辺について何回伸ばすかも復元する必要あり</li> </ul> <p>まず、この問題はほぼ最小費用循環流の双対問題そのものなので、構築がなければ難しくない。というか大体コレ <a href="https://atcoder.jp/contests/jag2015autumn/tasks/icpc2015autumn_j">J - Longest Shortest Path</a></p> <blockquote><p><strong>最小費用循環流</strong>:</p> <p>$N$ 頂点 $M$ 辺の有向グラフが与えられる。各辺 $e$ には非負の容量 $c_e$ と(非負とは限らない)コスト $d_e$ が付いている。辺ごとに $0 \le f_e \le c_e$を満たす流量 $f_e$ を割り当てる。頂点ごとに流量の出入りが等しい必要がある。 $\sum_e f_e d_e$を最小化せよ。</p> <p><strong>最小費用循環流の双対問題</strong>:</p> <p>$N$ 頂点 $M$ 辺の有向グラフが与えられる。各辺 $e$ には非負整数 $c_e$ と整数 $d_e$ が付いている。頂点ごとにポテンシャル $p_v$ を割り当て、$- \sum_{e = (u \to v)} c_e \max(0, p_v - p_u - d_e)$ を最大化せよ。</p></blockquote> <p>この2つの問題はLP双対から得られる問題である。そのため、同じグラフに対するこの2つの問題の答えは必ず一致する。なお、(答えが $-1$ 倍されるが)後者の目的関数は「$\sum_{e = (u \to v)} c_e \max(0, p_v - p_u - d_e)$ を最小化」としたほうが見通しがいいと思う。参考: <a href="https://www.slideshare.net/wata_orz/ss-91375739">&#x53CC;&#x5BFE;&#x6027; | PPT</a> 。</p> <h2 id="今回の問題">今回の問題</h2> <p>は、牛ゲー(最短経路問題の双対)に思いを馳せると、$(u, v, c, d) = (N, 1, \inf, -K), (1, N, \inf, K)$ の $2$ 辺を追加して、最小費用循環流の双対問題を解けばよいことがわかる。追加した $2$ 辺は $p_N - p_1 = K$という制約を追加することに対応する。これは普通の逐次最短路法+少しの工夫でもいいし、強力なアルゴリズムを用いてもいい (参考: <a href="https://judge.yosupo.jp/problem/min_cost_b_flow">https://judge.yosupo.jp/problem/min_cost_b_flow</a> 周りの諸々)。つまり、この問題の本質は「最小費用循環流の双対問題の解 $p_v$ をどうやって復元するのか?」という点になる。</p> <h2 id="p_v-の復元方法">$p_v$ の復元方法</h2> <p>結論から言うと、大体のライブラリは内部にこの $p$ を変数として持っているし、最小費用循環流を流せるだけ流した後にベルマンフォード法を行えば直接求まる。どういうことだろうか?</p> <p>まず、最小費用循環流の基礎として、次が成立する。</p> <ul> <li>最小費用循環流において、ある流量 $f$ が最適解である $\Leftrightarrow$ 残余グラフに負閉路が存在しない</li> </ul> <p>($\Rightarrow$)は自明、($\Leftarrow$)は自明ではないけど、現在の流量 $f$ と最適解の流量 $f'$ の差分に注目すると示せる。</p> <p>また、負閉路が存在しない、またその時のみ、残余グラフの(容量が $0$ でない)各辺について $p_v - p_u - d_e \le 0$ を満たすポテンシャル $p$ が存在する。これはベルマンフォード法などで計算可能。また、このポテンシャルは多くのライブラリが内部に直接変数として持っている。</p> <p>逆に言うと、流量条件を満たす流量 $f$ と、$f$ の残余グラフにおいて $p_v - p_u - d_e \le 0$を満たすポテンシャル $p$ が見つけられたなら、そのペア $(f, p)$ は $f$ が最適解であるという証拠になる。…というわけで <a href="https://judge.yosupo.jp/problem/min_cost_b_flow">https://judge.yosupo.jp/problem/min_cost_b_flow</a> では、流量 $f$ に加えてこのポテンシャル $p$ も復元させている。</p> <h2 id="2つのポテンシャル">2つのポテンシャル</h2> <p>この記事では $2$ つのポテンシャル $p$ が登場している。ひとつは「最小費用循環流の双対問題」で紹介した $p$ であり、これが今回の問題を解くために必要なものである。もう一つは最小費用循環流の最適解の残余グラフにベルマンフォード法を行うと計算可能な $p$ である。後者が計算可能なのはわかったが、どうやって前者を計算すればいいのか?実は後者の $p$ をそのまま前者の $p$ にすればいい(ええー!)</p> <p>今までの話を再度まとめる。</p> <p><strong>計算可能なことがわかっているもの</strong> : 次の条件を満たすペア $(f, p)$</p> <ul> <li>各辺 $e$ について、$0 \le f_e \le c_e$</li> <li>各頂点について、流量の出入りの総和が等しい</li> <li>$f_e > 0$ を満たす辺 $e = (u \to v)$ について、$p_u - p_v + d_e \le 0$</li> <li>$f_e &lt; c_e$ を満たす辺 $e = (u \to v)$ について、$p_v - p_u - d_e \le 0$</li> </ul> <p>ここで、「最小費用循環流」の答えは $\sum_e f_e d_e$ となる。</p> <p><strong>計算したいもの</strong>: $\sum_{e = (u \to v)} c_e \max(0, q_v - q_u - d_e)$ が「最小費用循環流の答えの $-1$ 倍」となる $q$。</p> <p><strong>証明したいこと</strong>: $p$が求める $q$ のうちひとつであること。つまり、$\sum_{e = (u \to v)} c_e \max(0, p_v - p_u - d_e)$ = $- \sum_e f_e d_e$ を示せればよい。</p> <h2 id="証明">証明</h2> <p>$f_e &lt; c_e$ならば、$p_v - p_u - d_e \le 0$、つまり $\max(0, p_v - p_u - d_e) = 0$であるので、$\sum_{e = (u \to v)} c_e \max(0, p_v - p_u - d_e)$</p> <p>$= \sum_{e = (u \to v)} f_e \max(0, p_v - p_u - d_e)$ となる。</p> <p>また、$f_e > 0$ならば $p_v - p_u - d_e \ge 0$ なので、</p> <p>$= \sum_{e = (u \to v)} f_e (p_v - p_u - d_e)$ となる。</p> <p>そして、$f$ を単純閉路に分解して考えることで</p> <p>$= \sum_{e = (u \to v)} - f_e d_e$</p> <p>が言えるため、示せた。</p> <h2 id="結論">結論</h2> <p><a href="https://judge.yosupo.jp/problem/min_cost_b_flow">https://judge.yosupo.jp/problem/min_cost_b_flow</a> を解くライブラリを準備(or 何らかの方法で入手)しておけば、張ると通る</p> yosupo こういう出題は許されるのか?⇒競プロで学ぶ統計学でした? hatenablog://entry/820878482968944102 2023-09-20T00:07:50+09:00 2023-09-25T05:19:42+09:00 AtCoderではともかく、ほかのジャッジでは想定解の正当性の保証がされていない出題が行われることがあります。 想定解の正当性の保証が出来ていなくても、ジャッジが大量にケースを入れてちゃんと動いたからOK!という出題は可能なのか考えてみます。 例えば色々こだわると、次のような出題になると思います。 問題 長さ10000の数列が与えられる。各要素はそれぞれ1以上10000以下の整数である。次の条件を満たす部分列を探し、見つけたら出力せよ。 条件: ナンタラカンタラ ジャッジ方法 あなたの提出は次のようにジャッジされる。 以下を独立に100回行う。あなたのコードが少なくとも30ケースに対して正しい… <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/AtCoder">AtCoder</a>ではともかく、ほかのジャッジでは想定解の正当性の保証がされていない出題が行われることがあります。 想定解の正当性の保証が出来ていなくても、ジャッジが大量にケースを入れてちゃんと動いたからOK!という出題は可能なのか考えてみます。 例えば色々こだわると、次のような出題になると思います。</p> <blockquote><h1 id="問題">問題</h1> <p>長さ10000の数列が与えられる。各要素はそれぞれ1以上10000以下の整数である。次の条件を満たす部分列を探し、見つけたら出力せよ。</p> <ul> <li>条件: ナンタ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%AB%A5%F3">ラカン</a>タラ</li> </ul> <h1 id="ジャッジ方法">ジャッジ方法</h1> <p>あなたの提出は次のようにジャッジされる。</p> <ul> <li>以下を独立に100回行う。あなたのコードが少なくとも30ケースに対して正しい部分列を出力していた場合、ACとする。 <ul> <li>ランダムケースを一様に$[1,10000]^{10000}$からジャッジが生成する。これは事前に作成されたものではなく、ジャッジごとに新たに作り直される。</li> <li>このランダムケースをあなたのコードに入力し、部分列を出力した場合それが正当かを検証する</li> </ul> </li> </ul> <h1 id="解説">解説</h1> <p>解法コードについて、$[1, 10000]^{10000}$について正しい部分列を出力するケースの割合を $p$ とする。これはその解法コードのランダムケースに対して正しく動く確率である。</p> <p>もし確率$p \geq 0.5$で正しい部分列を出力する解があれば、そのコードは&lt;十分高い確率>でACする。</p> <p>私たちジャッジは次の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D2%A5%E5%A1%BC%A5%EA%A5%B9%A5%C6%A5%A3%A5%C3%A5%AF">ヒューリスティック</a>を行う解答を用意しました。</p> <ul> <li><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%D2%A5%E5%A1%BC%A5%EA%A5%B9%A5%C6%A5%A3%A5%C3%A5%AF">ヒューリスティック</a>: ナンタ<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%E9%A5%AB%A5%F3">ラカン</a>タラ</li> </ul> <p>そして、この解答に同様にランダムケースを5000ケース入力したら、4000ケースに対して正しい部分列を出力した。もしこの想定解法が正しく動く確率$p$が$p &lt; 0.5$ならば、ランダムケースを5000ケース入れて4000ケースに対して正しく動く確率は&lt;ハチャメチャ低い確率>である。よって、このジャッジ解は$p \geq 0.5$であると考えられる。</p></blockquote> <h1 id="疑問">疑問</h1> <ul> <li>このような問題は許容されるのか?</li> <li>「よって、このジャッジ解は$p \geq 0.5$であると考えられる。」これはどういうことなの?</li> <li>許容される場合、ジャッジが事前に行うべきテストの量(今回でいうと4000/5000AC)は何らかの式で計算できるのか?</li> </ul> <h1 id="考察-大嘘解法の可能性は消せない">考察: 大嘘解法の可能性は消せない</h1> <p>例えば想定解が$p=0.01$であったとしても、確率$0.01^{5000}$で5000ケースに連続ACする。つまり、想定解が大嘘である可能性というのは0には出来ない。</p> <p>一方で、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B1%A7%C3%E8%C0%FE">宇宙線</a>、突然writerの頭に隕石が、<a class="keyword" href="https://d.hatena.ne.jp/keyword/AtCoder">AtCoder</a>がハッキングを食らう、など、そもそもコンテストが台無しになる可能性がそもそもそれなりにある。</p> <h1 id="典型ミス-想定解はハチャメチャ低い確率でp--05-を言うには追加の仮定が必要">典型ミス: 「想定解は(ハチャメチャ低い確率)でp &lt; 0.5」 を言うには追加の仮定が必要</h1> <p>P(4000ケースAC | ($p \geq 0.5$))とP(($p \geq 0.5$) | 4000ケースAC)は違うという話。例えば、</p> <ul> <li>こういう問題を作る</li> <li>$p=0.01$の解法を作って5000ケース入れる</li> <li>4000ケースACしたら出題</li> </ul> <p>これをとんでもない回数、例えば$100^{5000}$回行うと、このような問題が大量に出題され、そしてその全てが$p = 0.01$の大嘘解法となる。</p> <p>もちろんこれは$100^{5000}$回の試行を要求している時点で非現実的である。全く効果のない薬をたくさん作ってテストし続けるとどれかは効果がありそうな結果が出てくる、というのと似た話。</p> <ul> <li>「4000/5000ケースACした」かつ「大量に試行していない」 => 「想定解は$p &lt; 0.5$であると考えられる」</li> <li>「4000/5000ケースACした」かつ「$p$の事前分布が一様分布であると仮定する」 => 「想定解は(ハチャメチャ低い確率)で$p &lt; 0.5$」</li> </ul> <p>のように、追加で何らかの仮定が必要で、「4000/5000ケースACした」だけから「想定解は(ハチャメチャ低い確率)で$p &lt; 0.5$」は数学的には言えない…ハズ</p> <h1 id="有意水準"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%CD%AD%B0%D5%BF%E5%BD%E0">有意水準</a></h1> <p>$p$の事前分布はわからないだろうし、(今現在の)競プロ界隈のスタンス的に「writerが大量に試行していないから正しい」も広く受け入れられないと思う。なので$p$についてなにも言えなくて困る…と思いきや、こういう時のために<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CD%AD%B0%D5%BF%E5%BD%E0">有意水準</a>というのがあるらしい(統計初心者)。つまり、 「$p &lt; 0.5$なのに4000/5000ACするとんでもない確率を引いた」or「$p \geq 0.5$である」 ということならば言えるので、これで物事をやっていこうという話。</p> <p>「確率 $q$ を引いた」or「X」が言えたとして、例えば $q$ が一生かけても引けないぐらいの確率(例: (1000年 / 試行にかかる時間) * q &lt; 1)ならXは正しいと仮定して人生に問題はない?(例: 隕石は衝突しないと思い込んで行動しても人生に問題はない?)</p> <p>薬の検定などと違い、テストケースをいくらでも簡単に増やせるのが強みで、それこそ$q \leq 2^{-256}$とかが達成できる問題も少なくないと思う。世の中は$2^{-256}$は起こらないということで回っているはず(例: 適当にEd25519の<a class="keyword" href="https://d.hatena.ne.jp/keyword/%C8%EB%CC%A9%B8%B0">秘密鍵</a>を当てられる確率が$2^{-256}$)なので、これなら大丈夫そう。</p> <h1 id="結論">結論</h1> <ul> <li>「$p &lt; 0.5$なのに4000/5000ACするとんでもない確率を引いた」or「$p \geq 0.5$である」 ということならば言える。</li> <li>こういう思想を(競プロで)許すか、許すとしてどのぐらいの確率からは個人次第。</li> </ul> <p>自分の思想: とんでもない確率が$2^{-64}$とかならOK</p> <h1 id="思想についての余談">思想についての余談</h1> <ul> <li>そもそも乱択に関する思想 <ul> <li>想定解の通る確率がケースごとに$p$以上が証明できていればOK派閥</li> <li>想定解の通る確率が全体で$p$以上が証明できていればOK派閥 <ul> <li>ロリハ使うならケース数を公開しろ派閥がおそらくここ</li> </ul> </li> <li>想定解は決定的(=確率1)<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>であるべき派閥</li> </ul> </li> <li>(1, 2番目の思想の場合)許せる確率$p$はいくつか <ul> <li>全体で$\frac{1}{1000}$とか</li> <li>全体で$2^{-64}$とか(ハッシュの衝突など、世の中で0とされている確率ぐらい)</li> <li>Full feedbackかにも依存しそう</li> </ul> </li> </ul> <p>自分の思想: ケースごとに$10^{-6}$ぐらいならええやろ派閥</p> yosupo ちょっと速いかもしれないローリングハッシュ hatenablog://entry/820878482955932661 2023-08-06T18:19:42+09:00 2023-09-19T23:32:58+09:00 追記:速くなってませんでした!sorry https://x.com/yosupot/status/1689337328547016704?s=20 競技プログラミングではmod 261 - 1のローリングハッシュが安全性と速度のバランスが良く、広く使われています。 詳しくは https://qiita.com/keymoon/items/11fac5627672a6d6a9f6 などの記事が有用です。 このmod 261 - 1のmodintをより高速化することを試みます。先ほどの記事や、適当にライブラリを確認すると*1*2、乗算は以下の実装方法が広く使われています using u64 = … <p><span style="color: #d32f2f">追記:速くなってませんでした!sorry</span> <a href="https://x.com/yosupot/status/1689337328547016704?s=20">https://x.com/yosupot/status/1689337328547016704?s=20</a></p> <p><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%A5%B5%BB%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">競技プログラミング</a>ではmod 2<sup>61</sup> - 1のローリングハッシュが安全性と速度のバランスが良く、広く使われています。 詳しくは <a href="https://qiita.com/keymoon/items/11fac5627672a6d6a9f6">https://qiita.com/keymoon/items/11fac5627672a6d6a9f6</a> などの記事が有用です。</p> <p>このmod 2<sup>61</sup> - 1のmodintをより高速化することを試みます。先ほどの記事や、適当にライブラリを確認すると<a href="#f-b0b98803" name="fn-b0b98803" title="https://nyaannyaan.github.io/library/internal/internal-hash.hpp">*1</a><a href="#f-b2d22f85" name="fn-b2d22f85" title="https://hitonanode.github.io/cplib-cpp/number/modint_mersenne61.hpp.html">*2</a>、乗算は以下の実装方法が広く使われています</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synStatement">using</span> u64 = <span class="synType">unsigned</span> <span class="synType">long</span> <span class="synType">long</span>; <span class="synStatement">using</span> u128 = <span class="synType">unsigned</span> __int128; <span class="synType">const</span> u64 MOD = (<span class="synConstant">1ULL</span> &lt;&lt; <span class="synConstant">61</span>) - <span class="synConstant">1</span>; u64 <span class="synIdentifier">mul</span>(u64 a, u64 b) { u128 t = (u128)(a) * b; t = (t &gt;&gt; <span class="synConstant">61</span>) + (t &amp; MOD); <span class="synStatement">return</span> (t &gt;= MOD) ? t - MOD : t; } </pre> <p>ここで、値をそのままではなく8倍して管理することを考えます。こうすると<code>if (t &gt;= MOD)</code>相当の処理がoverflow checkになり、雰囲気的に良さそうな気がします。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink>u64 <span class="synIdentifier">mul2</span>(u64 a8, u64 b8) { u128 c = (u128)(a8) * b8; u64 x = (c &gt;&gt; <span class="synConstant">67</span> &lt;&lt; <span class="synConstant">3</span>), y = (c &lt;&lt; <span class="synConstant">61</span> &gt;&gt; <span class="synConstant">64</span>); u64 z; <span class="synStatement">if</span> (<span class="synIdentifier">__builtin_uaddll_overflow</span>(x, y, &amp;z)) z -= MOD &lt;&lt; <span class="synConstant">3</span>; <span class="synStatement">return</span> z; } u64 <span class="synIdentifier">mul</span>(u64 a, u64 b) { u64 t = <span class="synIdentifier">mul2</span>(a * <span class="synConstant">8</span>, b * <span class="synConstant">8</span>) / <span class="synConstant">8</span>; <span class="synStatement">if</span> (t == MOD) t = <span class="synConstant">0</span>; <span class="synStatement">return</span> t; } </pre> <p>実際に確認してみましょう。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgodbolt.org%2Fz%2FW3c5xYTKx" title="Compiler Explorer - C++ (x86-64 gcc 13.2)" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://godbolt.org/z/W3c5xYTKx">godbolt.org</a></cite></p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20230806/20230806181437.png" width="1200" height="514" loading="lazy" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>mul2のほうがめちゃくちゃすっきりしているのが確認できます。<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%DE%A5%B8%A5%C3%A5%AF%A5%CA%A5%F3%A5%D0%A1%BC">マジックナンバー</a>がなく、本当に正しいのかこれという感じですが、読むと正しそうに思えます。</p> <p>実際に O(N log<sup>2</sup> N) SAを実装してみます</p> <ul> <li>before 4368ms: <a href="https://judge.yosupo.jp/submission/154054">https://judge.yosupo.jp/submission/154054</a></li> <li>after 3692ms: <a href="https://judge.yosupo.jp/submission/154056">https://judge.yosupo.jp/submission/154056</a></li> </ul> <p>2割ほど早くなりました</p> <h2 id="注記">注記</h2> <ul> <li>まだあんまり使ってないので、バグってるかも</li> <li>そもそも従来のmodintのほうに改善の余地がありそう?<a href="https://twitter.com/yosupot/status/1688102890861395968">tweet</a></li> <li>値を[0, MOD]で管理する都合上比較が汚くなってしまう これなんとかなるのかな? -> <a href="https://twitter.com/noshi91/status/1688130780718092288">https://twitter.com/noshi91/status/1688130780718092288</a></li> </ul> <div class="footnote"> <p class="footnote"><a href="#fn-b0b98803" name="f-b0b98803" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://nyaannyaan.github.io/library/internal/internal-hash.hpp">https://nyaannyaan.github.io/library/internal/internal-hash.hpp</a></span></p> <p class="footnote"><a href="#fn-b2d22f85" name="f-b2d22f85" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://hitonanode.github.io/cplib-cpp/number/modint_mersenne61.hpp.html">https://hitonanode.github.io/cplib-cpp/number/modint_mersenne61.hpp.html</a></span></p> </div> yosupo Multiuni 2020 Day10 F hatenablog://entry/4207112889963833072 2023-02-17T01:28:42+09:00 2023-02-17T01:35:07+09:00 問題概要 問題 長さNのカッコ列 $a_1,a_2,\cdots,a_n$ が与えられる。これは正しく閉じているとは限らない。各文字は32bit非負整数の重み $b_1,b_2,\cdots,b_n$ を持っている。 カッコ列Sに対して、 $f(S)$ を次のように定義する $S$ が ()を部分文字列として持っているかぎりそれを削除し続ける、その時の最終的な文字列。 $f(S)$はどのindexの文字を残すかまで含めて一意に定まることに注意。 次の $Q$ 個のクエリを処理。 1 x y: $b_x$を $y$ に変更する(問題文には $a_x \to 1 - a_x$ もすると書かれている… <h1 id="問題概要">問題概要</h1> <p><a href="https://ac.nowcoder.com/acm/contest/5675/F">問題</a></p> <p>長さNのカッコ列 $a_1,a_2,\cdots,a_n$ が与えられる。これは正しく閉じているとは限らない。各文字は32bit非負整数の重み $b_1,b_2,\cdots,b_n$ を持っている。</p> <p>カッコ列Sに対して、 $f(S)$ を次のように定義する</p> <ul> <li>$S$ が <code>()</code>を部分文字列として持っているかぎりそれを削除し続ける、その時の最終的な文字列。</li> </ul> <p>$f(S)$はどのindexの文字を残すかまで含めて一意に定まることに注意。</p> <p>次の $Q$ 個のクエリを処理。</p> <ul> <li><code>1 x y</code>: $b_x$を $y$ に変更する(問題文には $a_x \to 1 - a_x$ もすると書かれているが、これは嘘)</li> <li><code>2 l r</code>: $f(S[l..r])$ の重みを $c_1, c_2, \cdots, c_k$ としたときの、$\max (c_1, c_2, \cdots, c_k), \mathrm{nand}(...\mathrm{nand}(\mathrm{nand}(2^{32}-1, c_1), c_2), ..., c_k)$を求める。この2つをxorしたものを出力する。</li> <li><code>3 l r</code>: $l..r$文字目と$r+1..n$文字目が入れ替わるようにswapする</li> </ul> <p>制約</p> <ul> <li>$N \leq 2 \times 10^{6}$</li> <li>$Q \leq 2 \times 10^{5}$</li> </ul> <h1 id="解法">解法</h1> <p>クエリ2で求めるmax / nandは共にモノイドの演算として考えることができる。</p> <p>カッコ列から <code>()</code> を取り除き続けたときの最終的な文字列の長さが平衡二分木に乗るのはそこそこ有名。最終的な文字列は <code>))..)(..((</code>という形になるので、 <code>)</code>と <code>(</code> の個数を <code>x, y</code> とすると、 <code>op((lx, ly), (rx, ry)) = (lx + rx - min(ly, rx), ly + ry - min(ly, ry)</code> のような演算ができる。</p> <p>今回の問題で同様のことをすると、ノードごとに</p> <ul> <li><code>))..)</code>の長さ <code>ln</code></li> <li><code>((..(</code>の長さ <code>rn</code></li> <li><code>))..)</code>の重みの総和 <code>lval</code></li> <li><code>((..(</code>の重みの総和 <code>rval</code></li> </ul> <p>を持たせたくなるけど、これだとうまくノードがマージできない。</p> <p>ここで、次の3つのパターンに限ればmerge可能なことに注目する。</p> <ul> <li><code>l-&gt;rn = 0</code></li> <li><code>r-&gt;ln = 0</code></li> <li><code>l-&gt;rn = r-&gt;ln</code></li> </ul> <p>「すべての葉以外のノードがこの3つの条件のいずれかを満たす」ような、葉に値を持たせる平衡二分木を管理する。</p> <p>この追加条件を保つように<a class="keyword" href="http://d.hatena.ne.jp/keyword/splay">splay</a> treeを改造する。</p> <p>方針としては、次のクエリを実装する。</p> <ul> <li><code>lsplit(node, k)</code>: ノードを2つに分割する。左のノードに対応する文字列 $S$ について、$f(S)$ は <code>))..)</code> ($k$ 文字)。</li> <li><code>rsplit(node, k)</code>: ノードを2つに分割する。右のノードに対応する文字列 $S$ について、$f(S)$ は <code>((..(</code>($k$ 文字)。</li> </ul> <p>これらの関数が実装できると、通常の平衡二分木のようにmergeが実装できる。mergeの引数が上記の3条件を満たさない場合でも、lsplitかrsplitを呼ぶことでmerge可能な形に変形できる。</p> <p>lsplitやrsplitは、このmerge関数が実装できれば実装できる。つまり相互<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>みたいな感じになる。</p> <h1 id="計算量">計算量</h1> <p>何もかもが謎</p> <ul> <li>手元で適当に試すと $O(\log N) / \mathrm{query}$ っぽい挙動をするが、はたして…</li> <li>実はもう少し違う方針で $O(\log ^2 N) / \mathrm{query}$ は達成できるが、これはTLEした</li> <li>writer解も謎の<a class="keyword" href="http://d.hatena.ne.jp/keyword/splay">splay</a> treeっぽいことをしていた、editorialがないのでこれも計算量は謎</li> </ul> <h1 id="コード">コード</h1> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant">&lt;cstdio&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;cassert&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;memory&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;algorithm&gt;</span> <span class="synPreProc">#include </span><span class="synConstant">&lt;vector&gt;</span> <span class="synStatement">using</span> <span class="synType">namespace</span> <span class="synConstant">std</span>; <span class="synStatement">using</span> uint = <span class="synType">unsigned</span> <span class="synType">int</span>; <span class="synType">struct</span> Monoid { uint mx, zero, one; <span class="synIdentifier">Monoid</span>() { mx = <span class="synConstant">0</span>; zero = <span class="synConstant">0</span>; one = -<span class="synConstant">1</span>; } <span class="synIdentifier">Monoid</span>(uint x) { mx = x; zero = -<span class="synConstant">1</span>; one = ~x; } uint <span class="synIdentifier">eval</span>() { <span class="synStatement">return</span> mx ^ one; } }; Monoid <span class="synStatement">operator</span>+(<span class="synType">const</span> Monoid&amp; l, <span class="synType">const</span> Monoid&amp; r) { Monoid m; m.mx = <span class="synIdentifier">max</span>(l.mx, r.mx); m.zero = (l.zero &amp; r.one) | (~l.zero &amp; r.zero); m.one = (l.one &amp; r.one) | (~l.one &amp; r.zero); <span class="synStatement">return</span> m; } <span class="synType">struct</span> Node; <span class="synStatement">using</span> NP = <span class="synType">unique_ptr</span>&lt;Node&gt;; <span class="synType">struct</span> Node { NP l = <span class="synStatement">nullptr</span>, r = <span class="synStatement">nullptr</span>; <span class="synType">int</span> sz = -<span class="synConstant">1</span>; <span class="synType">int</span> ln, rn; Monoid lval, rval; <span class="synIdentifier">Node</span>() {} <span class="synComment">// leaf node, true='(', false=')'</span> <span class="synIdentifier">Node</span>(<span class="synType">bool</span> type, uint x) : <span class="synIdentifier">sz</span>(<span class="synConstant">1</span>) { <span class="synStatement">if</span> (!type) { ln = <span class="synConstant">1</span>; rn = <span class="synConstant">0</span>; lval = <span class="synIdentifier">Monoid</span>(x); rval = <span class="synIdentifier">Monoid</span>(); } <span class="synStatement">else</span> { ln = <span class="synConstant">0</span>; rn = <span class="synConstant">1</span>; lval = <span class="synIdentifier">Monoid</span>(); rval = <span class="synIdentifier">Monoid</span>(x); } } <span class="synComment">// non leaf node</span> <span class="synIdentifier">Node</span>(NP _l, NP _r) : <span class="synIdentifier">l</span>(<span class="synIdentifier">move</span>(_l)), <span class="synIdentifier">r</span>(<span class="synIdentifier">move</span>(_r)), <span class="synIdentifier">sz</span>(l-&gt;sz + r-&gt;sz) { <span class="synIdentifier">assert</span>(l &amp;&amp; r); <span class="synStatement">if</span> (l-&gt;rn == r-&gt;ln) { ln = l-&gt;ln; rn = r-&gt;rn; lval = l-&gt;lval; rval = r-&gt;rval; } <span class="synStatement">else</span> <span class="synStatement">if</span> (l-&gt;rn == <span class="synConstant">0</span>) { ln = l-&gt;ln + r-&gt;ln; rn = r-&gt;rn; lval = l-&gt;lval + r-&gt;lval; rval = r-&gt;rval; } <span class="synStatement">else</span> <span class="synStatement">if</span> (r-&gt;ln == <span class="synConstant">0</span>) { ln = l-&gt;ln; rn = l-&gt;rn + r-&gt;rn; lval = l-&gt;lval; rval = l-&gt;rval + r-&gt;rval; } <span class="synStatement">else</span> { <span class="synIdentifier">assert</span>(<span class="synConstant">false</span>); } } }; <span class="synType">pair</span>&lt;NP, NP&gt; <span class="synIdentifier">lsplit</span>(NP x, <span class="synType">int</span> k); <span class="synType">pair</span>&lt;NP, NP&gt; <span class="synIdentifier">rsplit</span>(NP x, <span class="synType">int</span> k); NP <span class="synIdentifier">merge</span>(NP l, NP r) { <span class="synStatement">if</span> (!l) <span class="synStatement">return</span> r; <span class="synStatement">if</span> (!r) <span class="synStatement">return</span> l; <span class="synStatement">if</span> (l-&gt;rn == <span class="synConstant">0</span> || r-&gt;ln == <span class="synConstant">0</span> || l-&gt;rn == r-&gt;ln) { <span class="synStatement">return</span> <span class="synIdentifier">NP</span>(<span class="synStatement">new</span> <span class="synIdentifier">Node</span>(<span class="synIdentifier">move</span>(l), <span class="synIdentifier">move</span>(r))); } <span class="synStatement">if</span> (l-&gt;rn &lt; r-&gt;ln) { <span class="synType">auto</span> u = <span class="synIdentifier">lsplit</span>(<span class="synIdentifier">move</span>(r), l-&gt;rn); <span class="synStatement">return</span> <span class="synIdentifier">NP</span>( <span class="synStatement">new</span> <span class="synIdentifier">Node</span>(<span class="synIdentifier">NP</span>(<span class="synStatement">new</span> <span class="synIdentifier">Node</span>(<span class="synIdentifier">move</span>(l), <span class="synIdentifier">move</span>(u.first))), <span class="synIdentifier">move</span>(u.second))); } <span class="synStatement">else</span> { <span class="synType">auto</span> u = <span class="synIdentifier">rsplit</span>(<span class="synIdentifier">move</span>(l), r-&gt;ln); <span class="synStatement">return</span> <span class="synIdentifier">NP</span>( <span class="synStatement">new</span> <span class="synIdentifier">Node</span>(<span class="synIdentifier">move</span>(u.first), <span class="synIdentifier">NP</span>(<span class="synStatement">new</span> <span class="synIdentifier">Node</span>(<span class="synIdentifier">move</span>(u.second), <span class="synIdentifier">move</span>(r))))); } } <span class="synType">template</span>&lt;<span class="synType">class</span> F&gt; <span class="synType">pair</span>&lt;NP, NP&gt; <span class="synIdentifier">split2</span>(NP x, F f) { <span class="synType">int</span> type = <span class="synIdentifier">f</span>(x); <span class="synStatement">if</span> (type == <span class="synConstant">0</span>) { <span class="synStatement">return</span> {<span class="synIdentifier">move</span>(x-&gt;l), <span class="synIdentifier">move</span>(x-&gt;r)}; } <span class="synStatement">if</span> (type == -<span class="synConstant">1</span>) { <span class="synType">int</span> type2 = <span class="synIdentifier">f</span>(x-&gt;l); <span class="synStatement">if</span> (type2 == <span class="synConstant">0</span>) { <span class="synStatement">return</span> {<span class="synIdentifier">move</span>(x-&gt;l-&gt;l), <span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(x-&gt;l-&gt;r), <span class="synIdentifier">move</span>(x-&gt;r))}; } <span class="synStatement">if</span> (type2 == -<span class="synConstant">1</span>) { <span class="synComment">// zig-zig</span> <span class="synType">auto</span> u = <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x-&gt;l-&gt;l), f); <span class="synStatement">return</span> {<span class="synIdentifier">move</span>(u.first), <span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(u.second), <span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(x-&gt;l-&gt;r), <span class="synIdentifier">move</span>(x-&gt;r)))}; } <span class="synStatement">else</span> { <span class="synComment">// zig-zag</span> <span class="synType">auto</span> u = <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x-&gt;l-&gt;r), f); <span class="synStatement">return</span> {<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(x-&gt;l-&gt;l), <span class="synIdentifier">move</span>(u.first)), <span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(u.second), <span class="synIdentifier">move</span>(x-&gt;r))}; } } <span class="synStatement">else</span> { <span class="synType">int</span> type2 = <span class="synIdentifier">f</span>(x-&gt;r); <span class="synStatement">if</span> (type2 == <span class="synConstant">0</span>) { <span class="synStatement">return</span> {<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(x-&gt;l), <span class="synIdentifier">move</span>(x-&gt;r-&gt;l)), <span class="synIdentifier">move</span>(x-&gt;r-&gt;r)}; } <span class="synStatement">if</span> (type2 == <span class="synConstant">1</span>) { <span class="synComment">// zig-zig</span> <span class="synType">auto</span> u = <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x-&gt;r-&gt;r), f); <span class="synStatement">return</span> {<span class="synIdentifier">merge</span>(<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(x-&gt;l), <span class="synIdentifier">move</span>(x-&gt;r-&gt;l)), <span class="synIdentifier">move</span>(u.first)), <span class="synIdentifier">move</span>(u.second)}; } <span class="synStatement">else</span> { <span class="synComment">// zig-zag</span> <span class="synType">auto</span> u = <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x-&gt;r-&gt;l), f); <span class="synStatement">return</span> {<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(x-&gt;l), <span class="synIdentifier">move</span>(u.first)), <span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(u.second), <span class="synIdentifier">move</span>(x-&gt;r-&gt;r))}; } } } <span class="synType">pair</span>&lt;NP, NP&gt; <span class="synIdentifier">lsplit</span>(NP x, <span class="synType">int</span> k) { <span class="synIdentifier">assert</span>(<span class="synConstant">0</span> &lt;= k &amp;&amp; k &lt;= x-&gt;ln); <span class="synStatement">if</span> (k == <span class="synConstant">0</span>) { <span class="synStatement">return</span> {<span class="synStatement">nullptr</span>, <span class="synIdentifier">move</span>(x)}; } <span class="synStatement">else</span> <span class="synStatement">if</span> (k == x-&gt;ln) { <span class="synStatement">return</span> {<span class="synIdentifier">move</span>(x), <span class="synStatement">nullptr</span>}; } <span class="synStatement">return</span> <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x), [&amp;](<span class="synType">const</span> NP&amp; n) { <span class="synIdentifier">assert</span>(<span class="synConstant">0</span> &lt; k &amp;&amp; k &lt; n-&gt;ln); <span class="synType">int</span> lsz = n-&gt;l-&gt;ln; <span class="synStatement">if</span> (lsz == k) <span class="synStatement">return</span> <span class="synConstant">0</span>; <span class="synStatement">if</span> (k &lt; lsz) <span class="synStatement">return</span> -<span class="synConstant">1</span>; k -= lsz; <span class="synStatement">return</span> <span class="synConstant">1</span>; }); } <span class="synType">pair</span>&lt;NP, NP&gt; <span class="synIdentifier">rsplit</span>(NP x, <span class="synType">int</span> k) { <span class="synIdentifier">assert</span>(<span class="synConstant">0</span> &lt;= k &amp;&amp; k &lt;= x-&gt;rn); <span class="synStatement">if</span> (k == <span class="synConstant">0</span>) { <span class="synStatement">return</span> {<span class="synIdentifier">move</span>(x), <span class="synStatement">nullptr</span>}; } <span class="synStatement">else</span> <span class="synStatement">if</span> (k == x-&gt;rn) { <span class="synStatement">return</span> {<span class="synStatement">nullptr</span>, <span class="synIdentifier">move</span>(x)}; } <span class="synStatement">return</span> <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x), [&amp;](<span class="synType">const</span> NP&amp; n) { <span class="synIdentifier">assert</span>(<span class="synConstant">0</span> &lt; k &amp;&amp; k &lt; n-&gt;rn); <span class="synType">int</span> rsz = n-&gt;r-&gt;rn; <span class="synStatement">if</span> (rsz == k) <span class="synStatement">return</span> <span class="synConstant">0</span>; <span class="synStatement">if</span> (k &lt; rsz) <span class="synStatement">return</span> <span class="synConstant">1</span>; k -= rsz; <span class="synStatement">return</span> -<span class="synConstant">1</span>; }); } <span class="synType">pair</span>&lt;NP, NP&gt; <span class="synIdentifier">split</span>(NP x, <span class="synType">int</span> k) { <span class="synIdentifier">assert</span>(<span class="synConstant">0</span> &lt;= k &amp;&amp; k &lt;= x-&gt;sz); <span class="synStatement">if</span> (k == <span class="synConstant">0</span>) { <span class="synStatement">return</span> {<span class="synStatement">nullptr</span>, <span class="synIdentifier">move</span>(x)}; } <span class="synStatement">else</span> <span class="synStatement">if</span> (k == x-&gt;sz) { <span class="synStatement">return</span> {<span class="synIdentifier">move</span>(x), <span class="synStatement">nullptr</span>}; } <span class="synStatement">return</span> <span class="synIdentifier">split2</span>(<span class="synIdentifier">move</span>(x), [&amp;](<span class="synType">const</span> NP&amp; n) { <span class="synIdentifier">assert</span>(<span class="synConstant">0</span> &lt; k &amp;&amp; k &lt; n-&gt;sz); <span class="synType">int</span> lsz = n-&gt;l-&gt;sz; <span class="synStatement">if</span> (lsz == k) <span class="synStatement">return</span> <span class="synConstant">0</span>; <span class="synStatement">if</span> (k &lt; lsz) <span class="synStatement">return</span> -<span class="synConstant">1</span>; k -= lsz; <span class="synStatement">return</span> <span class="synConstant">1</span>; }); } <span class="synType">int</span> <span class="synIdentifier">main</span>() { <span class="synType">int</span> n, q; <span class="synIdentifier">scanf</span>(<span class="synConstant">&quot;</span><span class="synSpecial">%d</span><span class="synConstant"> </span><span class="synSpecial">%d</span><span class="synConstant">&quot;</span>, &amp;n, &amp;q); <span class="synType">vector</span>&lt;<span class="synType">int</span>&gt; <span class="synIdentifier">a</span>(n); <span class="synType">vector</span>&lt;uint&gt; <span class="synIdentifier">b</span>(n); <span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i &lt; n; i++) { <span class="synIdentifier">scanf</span>(<span class="synConstant">&quot;</span><span class="synSpecial">%d</span><span class="synConstant"> </span><span class="synSpecial">%d</span><span class="synConstant">&quot;</span>, &amp;(a[i]), &amp;(b[i])); } <span class="synType">auto</span> build = [&amp;](<span class="synType">auto</span> self, <span class="synType">int</span> l, <span class="synType">int</span> r) -&gt; NP { <span class="synStatement">if</span> (l + <span class="synConstant">1</span> == r) { <span class="synStatement">return</span> <span class="synIdentifier">NP</span>(<span class="synStatement">new</span> <span class="synIdentifier">Node</span>(a[l] == <span class="synConstant">1</span>, b[l])); } <span class="synType">int</span> mid = (l + r) / <span class="synConstant">2</span>; <span class="synStatement">return</span> <span class="synIdentifier">merge</span>(<span class="synIdentifier">self</span>(self, l, mid), <span class="synIdentifier">self</span>(self, mid, r)); }; NP tr = <span class="synIdentifier">build</span>(build, <span class="synConstant">0</span>, n); <span class="synStatement">for</span> (<span class="synType">int</span> ph = <span class="synConstant">0</span>; ph &lt; q; ph++) { <span class="synType">int</span> ty, l, r; <span class="synIdentifier">scanf</span>(<span class="synConstant">&quot;</span><span class="synSpecial">%d</span><span class="synConstant"> </span><span class="synSpecial">%d</span><span class="synConstant"> </span><span class="synSpecial">%d</span><span class="synConstant">&quot;</span>, &amp;ty, &amp;l, &amp;r); l--; <span class="synStatement">if</span> (ty == <span class="synConstant">1</span>) { <span class="synType">auto</span> t0 = <span class="synIdentifier">split</span>(<span class="synIdentifier">move</span>(tr), l + <span class="synConstant">1</span>); <span class="synType">auto</span> t1 = <span class="synIdentifier">split</span>(<span class="synIdentifier">move</span>(t0.first), l); <span class="synIdentifier">assert</span>(t1.second-&gt;sz == <span class="synConstant">1</span>); *t1.second = <span class="synIdentifier">Node</span>(t1.second-&gt;rn == <span class="synConstant">1</span>, r); tr = <span class="synIdentifier">merge</span>(<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(t1.first), <span class="synIdentifier">move</span>(t1.second)), <span class="synIdentifier">move</span>(t0.second)); } <span class="synStatement">else</span> <span class="synStatement">if</span> (ty == <span class="synConstant">2</span>) { <span class="synType">auto</span> t0 = <span class="synIdentifier">split</span>(<span class="synIdentifier">move</span>(tr), r); <span class="synType">auto</span> t1 = <span class="synIdentifier">split</span>(<span class="synIdentifier">move</span>(t0.first), l); <span class="synType">auto</span> val = t1.second-&gt;lval + t1.second-&gt;rval; <span class="synIdentifier">printf</span>(<span class="synConstant">&quot;</span><span class="synSpecial">%u\n</span><span class="synConstant">&quot;</span>, val.<span class="synIdentifier">eval</span>()); tr = <span class="synIdentifier">merge</span>(<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(t1.first), <span class="synIdentifier">move</span>(t1.second)), <span class="synIdentifier">move</span>(t0.second)); } <span class="synStatement">else</span> { <span class="synType">auto</span> t0 = <span class="synIdentifier">split</span>(<span class="synIdentifier">move</span>(tr), r); <span class="synType">auto</span> t1 = <span class="synIdentifier">split</span>(<span class="synIdentifier">move</span>(t0.first), l); tr = <span class="synIdentifier">merge</span>(<span class="synIdentifier">merge</span>(<span class="synIdentifier">move</span>(t1.first), <span class="synIdentifier">move</span>(t0.second)), <span class="synIdentifier">move</span>(t1.second)); } } } </pre> yosupo Suffix Automaton hatenablog://entry/26006613685413062 2021-01-31T16:02:15+09:00 2021-01-31T16:02:15+09:00 概要 文字列 $S$ のSuffix Automatonとは、ざっくりいうととても性質のいいDFA(決定性オートマトン)である。一番代表的な性質は次のとおりである。 $S$ の部分文字列全て、またそれらのみを受理する。 頂点数,辺数が $O(|S|)$、より正確には $|V| \leq (2|S| - 1), |E| \leq (3|S| - 4)$ $O(|S|)$ 時間で構築可能 このようなオートマトンが存在するということがまず非自明なのだが、これらに加えて、更に様々な良い性質がある。 構築 構成 最初に、どのようなオートマトンを作るのか(および、存在性の証明)を示す。結論から言うと、Su… <h1>概要</h1> <p>文字列 $S$ のSuffix Automatonとは、ざっくりいうととても性質のいい<a class="keyword" href="http://d.hatena.ne.jp/keyword/DFA">DFA</a>(決定性<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%C8%A5%DE%A5%C8%A5%F3">オートマトン</a>)である。一番代表的な性質は次のとおりである。</p> <ul> <li>$S$ の部分文字列全て、またそれらのみを受理する。</li> <li>頂点数,辺数が $O(|S|)$、より正確には $|V| \leq (2|S| - 1), |E| \leq (3|S| - 4)$</li> <li>$O(|S|)$ 時間で構築可能</li> </ul> <p>このような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%C8%A5%DE%A5%C8%A5%F3">オートマトン</a>が存在するということがまず非自明なのだが、これらに加えて、更に様々な良い性質がある。</p> <h1>構築</h1> <h2>構成</h2> <p>最初に、どのような<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%C8%A5%DE%A5%C8%A5%F3">オートマトン</a>を作るのか(および、存在性の証明)を示す。結論から言うと、Suffix Automatonは$\mathrm{rev}(S)$のCompressed Suffix Treeから<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B5%A1%B3%A3%C5%AA">機械的</a>に作ることが出来る。</p> <p>例えば、$S = "babcc"$、つまり $\mathrm{rev}(S) = "ccbab"$ の場合、Suffix Treeは次のようになる。黒色のノードは、そのノードを終端とする Suffix が存在することを表す。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20210131/20210131160146.jpg" alt="f:id:yosupo:20210131160146j:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>「次数 $1$ かつ白色のノード」を子のノードとマージしたものがCompressed Suffix Treeである。これと、$S$ の Suffix Automaton が次のようになる。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20210131/20210131160121.jpg" alt="f:id:yosupo:20210131160121j:plain" title="" class="hatena-fotolife" itemprop="image"></span></p> <p>ここで、左右のノードは一対一対応している。実際に、ノード7について、受理する文字列の集合が(revすると)全く同一である。他のノードに対しても同じ性質が成り立つ。</p> <p>このようなAutomatonが必ず存在することが、次の定理によりわかる。証明は容易なので略する。</p> <ul> <li>Compressed Suffix Treeの(根以外の)任意のノードについて、受理する文字列たちから最初の文字を削除した集合は、いくつかのノードの受理する文字列たちの直和で表せる。</li> </ul> <p>実際に、Compressed Suffix Treeの各ノードについて、上記の直和のノードたちから最初の文字で遷移を貼るだけでSuffix Automatonが構築できる。</p> <h2>頂点数 / 辺数</h2> <p>頂点数は明らかに線形である(一般に、$n$ 個の文字列からパトリシアを作ると頂点数は$O(n)$になる)。</p> <p>辺数はまずSuffix Automatonから(有向)全域木を取る。当然これの辺数はノード数 - 1である。全域木に含まれない辺それぞれについて、(全域木のパス + 含まれない辺)から生成される文字列たちを考える。すると</p> <ul> <li>全て $S$ の部分文字列</li> <li>文字列の間に、「片方が片方のprefix」という関係はない</li> </ul> <p>が成立するので、文字列の個数 = 全域木に含まれない辺数 は高々 $|S|$</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a></h2> <p>Compressed Suffix Tree / Suffix Automaton を対応を意識しながら並列で構築する。KMP<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>のように、$S$ の後ろ($\mathrm{rev}(S)$の先頭)に一文字ずつ文字を追加していく。これは Compressed Suffix Treeでは、(新しい)文字列全体をSuffix Treeに追加することに対応する。</p> <p>Compressed Suffix Tree / Suffix Automatonの情報として、次の情報を持つ。</p> <ul> <li>$\text{next}(\text{node}, \text{char})$ : Suffix Automatonの辺</li> <li>$\mathrm{link}(\mathrm{node})$ : Compressed Suffix Treeでの親ノード</li> <li>$\mathrm{len}(\mathrm{code})$ : nodeが受理する最長の文字列の長さ(=Suffix Treeでの一番下のノードの深さ)</li> <li>$\mathrm{last}$ : $S$ 全体を入れた時に受理するノード</li> </ul> <p>lenだけ不自然に感じるが、構築で必要になる。</p> <p>構築においての本質は、Suffix Automatonの次の性質である。</p> <ul> <li>(非空の文字列) $S$ の最後の文字を削除した文字列を $S'$ とする。Compressed Suffix Treeでの $S'$ / $S$ のパス上のノードを $n'<em>1, n'</em>2, \cdots, n'<em>l$ / $n_1, n_2, \cdots, n_<a class="keyword" href="http://d.hatena.ne.jp/keyword/m%24">m$</a> とする($n'</em>1 = n_1 = \mathrm{根}$)。このとき、$\mathrm{根}, \mathrm{next}(n'<em>1, x), \mathrm{next}(n'</em>2, x), \cdots, \mathrm{next}(n'_l, x)$ をランレングス圧縮したものが、$n_1, n_2, \cdots, n_<a class="keyword" href="http://d.hatena.ne.jp/keyword/m%24">m$</a> となる。</li> </ul> <p>構築では、新しい文字を後ろに追加した後この性質を満たすように様々な値をいじればよい。</p> <p>実際には新しいノードを作り、 $\mathrm{last}$ からlinkをたどっていく。そして</p> <ul> <li>$\mathrm{next}(n, x)$ が存在しない間 $\to$ 新しく作ったノードに$\mathrm{next}(n, x)$を張っていく</li> <li>$\mathrm{next}(n, x)$ が存在する場合。ノード $m = \mathrm{next}(n, x)$を分割(Clone)しないといけない場合がある。この判定に $\mathrm{len}$ を使う。$\mathrm{len}(n) + 1 = \mathrm{len}(m)$ ならば Cloneする必要がない。 <ul> <li>分割するためには、新規ノードを追加する。これはSuffix Treeで根から近い側に対応する。更に$m = \mathrm{next}(n, x)$ の間linkを辿り、nextを新規ノードに張り替える必要があることに注意。</li> </ul> </li> </ul> <p>構築の計算量について考える。頂点数、辺数が線形であるので大部分は大丈夫だが、唯一怪しいのは「ノードをCloneした後にnextを張り替える」部分である。実際には、「Compress Suffix Treeでの $\mathrm{last}$ ノードの高さ」をポテンシャルとすることで抑えることが出来る。</p> <h1>性質</h1> <p>その他の性質を列挙する</p> <ul> <li>Suffix Automatonは「Sの部分列、またそれのみを受理する」<a class="keyword" href="http://d.hatena.ne.jp/keyword/DFA">DFA</a>の中で頂点数が最小(らしい)</li> <li>$S$ のSuffixと、cloneされてないノードたちは1対1対応する。</li> </ul> <h1>拡張</h1> <p>Compressed Suffix Treeが自然に複数の文字列に対応できるように、Suffix Automatonも対応できる。</p> <p>lastを初期化して文字列追加、を同じ Suffix Automaton に繰り返せば良い。ここで、新しいノードが生まれない場合もあることに注意。</p> <h1>問題例</h1> <h2>$S$ の部分文字列の種類数</h2> <ul> <li>DAGのパスの総数となるので、DP出来る</li> <li>各頂点 $n$ について$\mathrm{len}(n) - \mathrm{len}(\mathrm{link}(n))$ を足すだけでもよい(これはCompressed Suffix Treeでのパスの長さ=受理する文字列数に対応する)。</li> </ul> <h2>$S, T$の共通部分列の種類数</h2> <ul> <li>$S$, $T$, $S$ + "$" + $T$ の部分列の個数から計算できる。</li> <li>${ S, T }$ から Suffix Automaton を作ることで直接計算できる。$S$ のSuffix, $T$ のSuffixのどちらからも到達可能なノードのみについて$\mathrm{len}(n) - \mathrm{len}(\mathrm{link}(n))$を足せば良い</li> </ul> <h1>参考</h1> <ul> <li><a href="https://codeforces.com/blog/entry/20861">https://codeforces.com/blog/entry/20861</a></li> <li><a href="https://cp-algorithms.com/string/suffix-automaton.html">https://cp-algorithms.com/string/suffix-automaton.html</a></li> <li><a href="https://w.atwiki.jp/uwicoder/pages/2842.html">https://w.atwiki.jp/uwicoder/pages/2842.html</a></li> </ul> <h1>使用例</h1> <h2>AC code of <a href="https://judge.yosupo.jp/problem/number_of_substrings">Number of Substrings</a></h2> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">struct</span> SuffixAutomaton { <span class="synType">struct</span> Node { unordered_map&lt;<span class="synType">char</span>, <span class="synType">int</span>&gt; next; <span class="synType">int</span> link, len; }; vector&lt;Node&gt; nodes; <span class="synType">int</span> last; SuffixAutomaton() { nodes.push_back({{}, -<span class="synConstant">1</span>, <span class="synConstant">0</span>}); last = <span class="synConstant">0</span>; } <span class="synType">void</span> push(<span class="synType">char</span> c) { <span class="synType">int</span> new_node = <span class="synType">int</span>(nodes.size()); nodes.push_back({{}, -<span class="synConstant">1</span>, nodes[last].len + <span class="synConstant">1</span>}); <span class="synType">int</span> p = last; <span class="synStatement">while</span> (p != -<span class="synConstant">1</span> &amp;&amp; nodes[p].next.find(c) == nodes[p].next.end()) { nodes[p].next[c] = new_node; p = nodes[p].link; } <span class="synType">int</span> q = (p == -<span class="synConstant">1</span> ? <span class="synConstant">0</span> : nodes[p].next[c]); <span class="synStatement">if</span> (p == -<span class="synConstant">1</span> || nodes[p].len + <span class="synConstant">1</span> == nodes[q].len) { nodes[new_node].link = q; } <span class="synStatement">else</span> { <span class="synComment">// clone node (q -&gt; new_q)</span> <span class="synType">int</span> new_q = <span class="synType">int</span>(nodes.size()); nodes.push_back({nodes[q].next, nodes[q].link, nodes[p].len + <span class="synConstant">1</span>}); nodes[q].link = new_q; nodes[new_node].link = new_q; <span class="synStatement">while</span> (p != -<span class="synConstant">1</span> &amp;&amp; nodes[p].next[c] == q) { nodes[p].next[c] = new_q; p = nodes[p].link; } } last = new_node; } }; <span class="synType">int</span> main() { string s; cin &gt;&gt; s; SuffixAutomaton sa; <span class="synStatement">for</span> (<span class="synType">char</span> c : s) { sa.push(c); } <span class="synType">int</span> m = <span class="synType">int</span>(sa.nodes.size()); ll ans = <span class="synConstant">0</span>; <span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">1</span>; i &lt; m; i++) { ans += sa.nodes[i].len - sa.nodes[sa.nodes[i].link].len; } cout &lt;&lt; ans &lt;&lt; endl; <span class="synStatement">return</span> <span class="synConstant">0</span>; } </pre> yosupo AGC 046 E Permutation Cover hatenablog://entry/26006613611791272 2020-08-08T22:46:09+09:00 2020-08-08T22:46:09+09:00 解けずに解説読んだ 本質 答えが-1かどうか <=> (max < 2*min)に気づくかどうか [1, 1, ..., 1, 2, 2, ..., 2]のパーツができる <=> ↑の条件は全部できる 思考反省 方向性を間違えた 出来た: 辞書順最小なので判定条件 出来たが使わなかった: パーツごとに独立 間違えた方針: 先頭のごちゃごちゃとかがあるから判定条件を書き下すのは無理だろう 考えたくねえ -> きっとなんらかの貪欲が最適なことが証明可能で、これを実装すればいいんだろう 正しい方針: とりあえず先頭にごちゃごちゃがない場合の判定条件を考える。 <p>解けずに解説読んだ</p> <h1>本質</h1> <ul> <li>答えが-1かどうか &lt;=> (max &lt; 2*min)に気づくかどうか</li> <li>[1, 1, ..., 1, 2, 2, ..., 2]のパーツができる &lt;=> ↑の条件は全部できる</li> </ul> <h1>思考反省</h1> <p>方向性を間違えた</p> <p>出来た: 辞書順最小なので判定条件 出来たが使わなかった: パーツごとに独立 間違えた方針: 先頭のごちゃごちゃとかがあるから判定条件を書き下すのは無理だろう 考えたくねえ -> きっとなんらかの貪欲が最適なことが証明可能で、これを実装すればいいんだろう 正しい方針: とりあえず先頭にごちゃごちゃがない場合の判定条件を考える。</p> yosupo 競プロ実装テクニック hatenablog://entry/26006613607232792 2020-07-30T22:15:04+09:00 2020-07-31T19:28:00+09:00 これはなに 実装力で戦える! ~競プロにおける実装テクニック14選~ - Qiita に触発された 競技プログラミングでコーディングの際気を付けていること - うさぎ小屋 を強く参考にしている 効果が高い or 一般性がありそう なことから書いたつもり 重要なこと 「競プロのきれいなコードと業務のきれいなコードは違う」と定期的に唱える。未来の自分 or 他の人 が読む必要がないことを仮定できるため、様々なバッドノウハウ(業務)が正当化される。(あえて過激なことを書くと、)「using namespace stdを使わない」などは逆にバッドノウハウ(競プロ)だと思っている。 -fsanitize… <h1>これはなに</h1> <p><a href="https://qiita.com/e869120/items/920a6e63435bf6efe539">&#x5B9F;&#x88C5;&#x529B;&#x3067;&#x6226;&#x3048;&#x308B;&#xFF01; &#xFF5E;&#x7AF6;&#x30D7;&#x30ED;&#x306B;&#x304A;&#x3051;&#x308B;&#x5B9F;&#x88C5;&#x30C6;&#x30AF;&#x30CB;&#x30C3;&#x30AF;14&#x9078;&#xFF5E; - Qiita</a> に触発された</p> <p><a href="https://kimiyuki.net/blog/2015/09/25/competitive-programming-coding/">&#x7AF6;&#x6280;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;&#x3067;&#x30B3;&#x30FC;&#x30C7;&#x30A3;&#x30F3;&#x30B0;&#x306E;&#x969B;&#x6C17;&#x3092;&#x4ED8;&#x3051;&#x3066;&#x3044;&#x308B;&#x3053;&#x3068; - &#x3046;&#x3055;&#x304E;&#x5C0F;&#x5C4B;</a> を強く参考にしている</p> <p>効果が高い or 一般性がありそう なことから書いたつもり</p> <h2>重要なこと</h2> <p><b>「競プロのきれいなコードと業務のきれいなコードは違う」</b>と定期的に唱える。未来の自分 or 他の人 が読む必要がないことを仮定できるため、様々な<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C9%A5%CE%A5%A6%A5%CF%A5%A6">バッドノウハウ</a>(業務)が正当化される。(あえて過激なことを書くと、)「using namespace stdを使わない」などは逆に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D0%A5%C3%A5%C9%A5%CE%A5%A6%A5%CF%A5%A6">バッドノウハウ</a>(競プロ)だと思っている。</p> <h2>-fsanitize=undefined,address / -D_GLIBCXX_DEBUG</h2> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant">&lt;iostream&gt;</span> <span class="synStatement">using</span> <span class="synType">namespace</span> std; <span class="synType">int</span> main() { <span class="synType">int</span> a[<span class="synConstant">10</span>]; cout &lt;&lt; a[<span class="synConstant">100</span>] &lt;&lt; endl; <span class="synStatement">return</span> <span class="synConstant">0</span>; } </pre> <p>このコードは当然未定義動作だが、これを <code>g++ main.cpp -g -fsanitize=undefined,address</code> と<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>して実行すると</p> <pre class="code" data-lang="" data-unlink>A.cpp:7:13: runtime error: index 100 out of bounds for type &#39;int [10]&#39; SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior A.cpp:7:13 in</pre> <p>などと出てくる。他にも様々な未定義動作をキャッチできる。速度に影響があるが、手元で使わない理由はないと思う。 他にも<code>-D_GLIBCXX_DEBUG</code>を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>オプションに入れると色々検知するようになるが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/mac">mac</a>で使えない…</p> <p>当然ながら <code>g++ main.cpp -g -fsanitize=undefined,address</code> などといちいち打つのは時間の無駄なので、aliasなどの機能を適時使うと良い。</p> <h2>コーディングスタイル</h2> <p>コーディングスタイル(インデント, space/tab, ...)は揃ってないより揃ってたほうがいい。有名規約で言うと <a href="https://google.github.io/styleguide/cppguide.html">Google C++ Style Guide</a> に従っている人が身近では多い気がする。</p> <p>インデントを手で整えるのは時間の無駄なので、エディタの自動整形に任せるべき。自分は以下の.clang-formatで<a class="keyword" href="http://d.hatena.ne.jp/keyword/vscode">vscode</a>に全部任せている。</p> <pre class="code" data-lang="" data-unlink>BasedOnStyle: &#39;Chromium&#39; IndentWidth: 4 AccessModifierOffset: -2 # for competitive programing AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: true AllowShortLoopsOnASingleLine: true AlwaysBreakTemplateDeclarations: false</pre> <p>いわゆる「きれいな」コーディングスタイルが(競プロで)常に正しいかというと怪しい。例えば<code>1 * 2 + 3 * 4</code>と<code>1*2 + 3*4</code>なら後者のほうが読みやすいと思う(自分はもう手癖で入れてしまうが…)。</p> <h2>using namespace std</h2> <p>575とも呼ばれる。<code>std::</code>があらゆるところから消せるのでコードがスッキリする。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CC%BE%C1%B0%B6%F5%B4%D6">名前空間</a>が汚染されるというデメリットがあるが、引っかかるのは無視できるぐらいの確率なので圧倒的にメリットのほうが大きいと思う。</p> <h2>マクロ / <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%CB%A5%DA%A5%C3%A5%C8">スニペット</a></h2> <p>マクロは俗に言う</p> <pre class="code" data-lang="" data-unlink>#define rep(i, n) for (int i = 0; i &lt; n; i++) #define all(v) v.begin(), v.end()</pre> <p>みたいなやつ。僕はマクロは使っていないが<code>fori</code> / <code>all</code> でこれらが出てくる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%CB%A5%DA%A5%C3%A5%C8">スニペット</a>を登録している。マクロを使っても競プロなら何のデメリットもない。</p> <p>とりあえず手でいちいち<code>for (int i = 0; i &lt; n; i++)</code>を書いているならば見直したほうがいい。</p> <h2>printf<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a> /デバッガ</h2> <p>個人的には多くの場合でデバッガよりprintf<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>のほうが速いと考えているが、デバッガのほうが強いときもある。assertで落ちたときに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%BF%A5%C3%A5%AF%A5%C8%A5%EC%A1%BC%A5%B9">スタックトレース</a>を確認するなど。僕は雑に<code>lldb</code>を使うが、これは改善の余地がある気がする。</p> <p>printf<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>するにしろ、printfをいちいちつけたり消したりするのは時間の無駄、僕は以下のようなマクロ(を強化したもの)を使っている。</p> <p>また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/vector">vector</a> / mapなども簡単に出力できるようにするといい。operator&lt;&lt;を<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AA%A1%BC%A5%D0%A1%BC%A5%ED%A1%BC%A5%C9">オーバーロード</a>するなど</p> <pre class="code" data-lang="" data-unlink>#ifdef LOCAL #define dbg(x) cerr &lt;&lt; __LINE__ &lt;&lt; &#34; : &#34; &lt;&lt; #x &lt;&lt; &#34; = &#34; &lt;&lt; (x) &lt;&lt; endl #else #define dbg(x) true #endif</pre> <h2>goto</h2> <p>競プロでもgotoを使ってメリットが有るタイミングは少ないと思うが例外もある、有名な例は多重ループの大域脱出がある。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">bool</span> ok = <span class="synConstant">false</span>; <span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i &lt; n; i++) { <span class="synStatement">for</span> (<span class="synType">int</span> j = <span class="synConstant">0</span>; j &lt; n; j++) { <span class="synStatement">if</span> (solve(i, j)) { ok = <span class="synConstant">true</span>; <span class="synStatement">break</span>; } } <span class="synStatement">if</span> (ok) <span class="synStatement">break</span>; } </pre> <p>が</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">bool</span> ok = <span class="synConstant">false</span>; <span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i &lt; n; i++) { <span class="synStatement">for</span> (<span class="synType">int</span> j = <span class="synConstant">0</span>; j &lt; n; j++) { <span class="synStatement">if</span> (solve(i, j)) { ok = <span class="synConstant">true</span>; <span class="synStatement">goto</span> loop_out; } } } <span class="synStatement">loop_out</span>: </pre> <p>のように書けるというやつ。2重だとありがたみが薄いが、競プロでは平然ともっと多重のループが出てくるので、そういう時に強い。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">bool</span> ok = [&amp;]() { <span class="synStatement">for</span> (<span class="synType">int</span> i = <span class="synConstant">0</span>; i &lt; n; i++) { <span class="synStatement">for</span> (<span class="synType">int</span> j = <span class="synConstant">0</span>; j &lt; n; j++) { <span class="synStatement">if</span> (solve(i, j)) <span class="synStatement">return</span> <span class="synConstant">true</span>; } } <span class="synStatement">return</span> <span class="synConstant">false</span>; }(); </pre> <p>このようにも書け、自分はこちらをよく使う。</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/%BA%C6%B5%A2">再帰</a>ラムダ</h2> <p>普通にdfsを書こうとすると</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink>vector&lt;vector&lt;<span class="synType">int</span>&gt;&gt; g; <span class="synType">void</span> dfs(<span class="synType">int</span> v, <span class="synType">int</span> parent) { <span class="synStatement">for</span> (<span class="synType">int</span> u: g[v]) { <span class="synStatement">if</span> (u == parent) <span class="synStatement">continue</span>; dfs(u, v); } } <span class="synType">int</span> main() { dfs(<span class="synConstant">0</span>, -<span class="synConstant">1</span>); } </pre> <p>となるが、これを</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">int</span> main() { vector&lt;vector&lt;<span class="synType">int</span>&gt;&gt; g; <span class="synType">auto</span> dfs = [&amp;](<span class="synType">auto</span> self, <span class="synType">int</span> v, <span class="synType">int</span> parent) -&gt; <span class="synType">void</span> { <span class="synStatement">for</span> (<span class="synType">int</span> u: g[v]) { <span class="synStatement">if</span> (u == parent) <span class="synStatement">continue</span>; self(self, u, parent); } }; dfs(dfs, <span class="synConstant">0</span>, -<span class="synConstant">1</span>); } </pre> <p>と書ける。独特な記法を覚える必要があるが、処理を上から下へと書けるのは大きい。なお、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E9%A5%E0%A5%C0%BC%B0">ラムダ式</a>は覚えると便利な事が多いので慣れると吉。</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/vector">vector</a>&lt;<a class="keyword" href="http://d.hatena.ne.jp/keyword/vector">vector</a>&lt;>>を短く書けるようにする</h2> <p>個人的おすすめ。<code>V&lt;int&gt;</code> / <code>VV&lt;int&gt;</code>でそれぞれ<code>vector&lt;int&gt;</code> / <code>vector&lt;vector&lt;int&gt;&gt;</code>になるようにしている。便利。</p> yosupo WTF C2 Triangular Lamps Hard 別解法 hatenablog://entry/26006613529603469 2020-03-03T19:04:35+09:00 2020-03-03T19:04:35+09:00 今回は、Nimberを使ってWTF C Triangular Lamps Easy / Hardを解いていこうと思います。 C1 このような問題では、操作で変わらない不変量を考えたくなります 具体的にはにを書き込めば、では不変量です。 なのでこれを適切にシフトしたものを考えて、うまくやるとOKです C2 先述の不変量は一般の場合ではあまり役に立ちません。 なぜならば、1の数が少なすぎるからです。 つまり不変量を求めたところで元の点に関して得られる情報量が少なすぎます。 じゃあどうするかというと、元々の想定解法では先述の不変量を同時に大量にばら撒きます。 もちろん適切に計算できるようなばらまきか… <p>今回は、Nimberを使って<a class="keyword" href="http://d.hatena.ne.jp/keyword/WTF">WTF</a> C Triangular Lamps Easy / Hardを解いていこうと思います。</p> <h2>C1</h2> <p>このような問題では、操作で変わらない不変量を考えたくなります</p> <p>具体的には<img src="https://chart.apis.google.com/chart?cht=tx&chl=%28x%2C%20y%29" alt="(x, y)"/>に<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cbinom%7Bx%20%2B%20y%7D%7Bx%7D%20%5Cpmod%202" alt=" \binom{x + y}{x} \pmod 2"/>を書き込めば、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%28x%20%5Cgeq%200%2C%20y%20%5Cgeq%200%29" alt=" (x \geq 0, y \geq 0)"/>では不変量です。 なのでこれを適切にシフトしたものを考えて、うまくやるとOKです</p> <h2>C2</h2> <p>先述の不変量は一般の場合ではあまり役に立ちません。 なぜならば、1の数が少なすぎるからです。 つまり不変量を求めたところで元の点に関して得られる情報量が少なすぎます。</p> <p>じゃあどうするかというと、元々の想定解法では先述の不変量を同時に大量にばら撒きます。 もちろん適切に計算できるようなばらまきかたをしないといけなくて、そういうことを考えると自然と<img src="https://chart.apis.google.com/chart?cht=tx&chl=%20%5Cbmod%203" alt=" \bmod 3"/>が出てきます。</p> <p>今回はもっと情報量の多い不変量を考えることにします。</p> <p>実際に、ある<img src="https://chart.apis.google.com/chart?cht=tx&chl=a" alt="a"/>について、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%28x%2C%20y%29" alt="(x, y)"/>に<img src="https://chart.apis.google.com/chart?cht=tx&chl=a%5E%20x%20%28a%2B1%29%5E%20y" alt="a^ x (a+1)^ y"/>を書き込むと、これは不変量になっています。ただし、要素は全て<img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cmathbb%7BF%7D_%20%7B2%5EN%7D" alt="\mathbb{F}_ {2^N}"/> とします。<img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cbmod%202" alt="\bmod 2"/>でダメなら<img src="https://chart.apis.google.com/chart?cht=tx&chl=%5Cmathbb%7BF%7D_%7B2%5EN%7D" alt="\mathbb{F}_{2^N}"/>というのは自然ですね。</p> <p>この不変量により、<img src="https://chart.apis.google.com/chart?cht=tx&chl=a" alt="a"/>を原子根にすると、<img src="https://chart.apis.google.com/chart?cht=tx&chl=%28a%20%2B%201%29%20%3D%20a%5Es" alt="(a + 1) = a^s"/>として、<img src="https://chart.apis.google.com/chart?cht=tx&chl=x%20%2B%20sy" alt="x + sy"/>が求まります。 もう一つ、他のaについても調べると、ある<img src="https://chart.apis.google.com/chart?cht=tx&chl=t" alt="t"/>について<img src="https://chart.apis.google.com/chart?cht=tx&chl=x%20%2B%20ty" alt="x + ty"/>が求まります。</p> <p>結果として、<img src="https://chart.apis.google.com/chart?cht=tx&chl=x%2C%20y" alt="x, y"/>が求まりました。</p> <p>実装としては、<img src="https://chart.apis.google.com/chart?cht=tx&chl=F_%7B2%5EN%7D" alt="F_{2^N}"/>で離散対数を求める必要があります。 ところで、最近Nimberで離散対数を求める問題が出ました: <a href="https://codeforces.com/contest/1314/problem/F">Problem - F - Codeforces</a> ちょうどいいですね。</p> <p>実装例: <a href="https://atcoder.jp/contests/wtf19-open/submissions/10507304">Submission #10507304 - World Tour Finals 2019 Open Contest</a></p> yosupo Library Checkerを支える技術 hatenablog://entry/26006613491983766 2020-01-02T00:16:17+09:00 2020-01-02T00:16:17+09:00 あけましておめでとうございます。これは Competitive Programming (2) Advent Calendar 2019 - Adventar の 14日目の記事です。あけましておめでとうございます。 この記事では、Library Checker の宣伝をします Library Checkerって? 競プロのライブラリを整備するために爆誕したサイトです。特徴としては、問題が全部ライブラリを整備する目的に特化していること、ケースジェネレーター、チェッカーなどが全て公開されていることが大きな特徴です 中身を全て公開することにより 誰でも問題の追加が出来る 誰でもケースの修正などが出… <p>あけましておめでとうございます。これは <a href="https://adventar.org/calendars/4587">Competitive Programming (2) Advent Calendar 2019 - Adventar</a> の 14日目の記事です。あけましておめでとうございます。</p> <p>この記事では、<a href="https://judge.yosupo.jp/">Library Checker</a> の宣伝をします</p> <h1>Library Checkerって?</h1> <p>競プロのライブラリを整備するために<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C7%FA%C3%C2">爆誕</a>したサイトです。特徴としては、問題が全部ライブラリを整備する目的に特化していること、<b>ケースジェネレーター、チェッカーなどが全て<a href="https://github.com/yosupo06/library-checker-problems">公開</a>されている</b>ことが大きな特徴です</p> <p>中身を全て公開することにより</p> <ul> <li>誰でも問題の追加が出来る</li> <li>誰でもケースの修正などが出来る</li> <li>CIに組み込める<a href="#f-071a6acf" name="fn-071a6acf" title="ざっくり言うと、自動で「ライブラリをちょっと修正するたびに全部の問題に投げ直す」ことが出来ます">*1</a></li> </ul> <p>などの、様々な利点を得ることが出来ます。理論上はO(使用人数)でケースが強くなっていくので、最強のテストケースが出来ると言う目論見です。</p> <h1>概要</h1> <p>こんな感じです</p> <p><figure class="figure-image figure-image-fotolife" title="こういう図をブログに貼りたかった"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20200101/20200101233401.png" alt="f:id:yosupo:20200101233401p:plain" title="f:id:yosupo:20200101233401p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>こういう図をブログに貼りたかった</figcaption></figure></p> <p>見ての通り、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>(<a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> Compute Platform)で動いています。</p> <p>一つ一つ紹介していきます</p> <h2>Problems</h2> <p><a href="https://github.com/yosupo06/library-checker-problems">library-checker-problems</a> で問題の情報を管理しています。</p> <p>ジェネレーターとかは全部<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>で、それを<a class="keyword" href="http://d.hatena.ne.jp/keyword/python">python</a>から叩く仕組みになっています。 目的上どの環境でも同じテストケースを吐き出すようにしないといけないので、とても苦労しています。(uniform_int_distributionは環境依存なんですよ、知っていましたか?)</p> <h2>Cloudbuild</h2> <p>図にやたら出てくるやつです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/yaml">yaml</a>ファイルにコマンドを書き並べるだけで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/github">github</a>にpushするたびに実行とかをやってくれるので乱用しています。今5種類ぐらい使っています。</p> <h2>ジャッジサーバー</h2> <p>dockerなどの既存のコンテナは時間計測 / メモリ計測がむずかったので、いっそのことと思い、unshare / cgroups / overlayfs などの(dockerの中で使われてる技術たち)を直接叩き、コンテナを作っています</p> <ul> <li>unshare: プロセスからネットワークのアクセスを禁止したり、mountを分離したりしてくれるすごいやつ</li> <li>cgroups: これがないと何も出来ない。プロセスのCPU、メモリ消費量とか諸々を制限してくれるすごいやつ</li> <li>overlayfs: プロセスから/tmpとか変な場所にファイルを書き込んでも、パッとやるとそれらを一瞬でなかったことにしてくれるすごいやつ</li> </ul> <p>ずっとサーバー借りてると高いので、preemptible(<a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a>のスポット<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>みたいなもの)を借り続けるという雑なことをしています。24時間(or もっと短い)で勝手に停止してしまうので、停止を検出したらCloud Functions(≒ <a class="keyword" href="http://d.hatena.ne.jp/keyword/AWS">AWS</a> Lambda)で自動再起動、うまくいくのかと思いましたが、今のところうまくいっています。</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a></h2> <p>Cloud <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>と言うフルマネージドのサービスでPostgre <a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>を立てています。バックアップなども取ってくれているようなので慢心しています。</p> <p>このサービスのコアで、全てが入っています。問題のテストケースもbytea型でそのまま入っています ええんかこれ?</p> <h2><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>サーバー</h2> <p>(最近正式サービスとなった)Cloud Runで動いています。理由はApp engineだとgRPCが動かなかったからです。 apiv1.yosupo.jp:443 で動いていて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>一覧は <a href="https://github.com/yosupo06/library-checker-judge/blob/master/api/proto/library_checker.proto">library-checker-judge/library_checker.proto at master &middot; yosupo06/library-checker-judge &middot; GitHub</a> です。(RESTではないので、<a href="https://github.com/ktr0731/evans">GitHub - ktr0731/evans: Evans: more expressive universal gRPC client</a> のようなgRPCクライアントを使わないと何も見れないです)</p> <h2>フロントエンド</h2> <p>言ってしまえば<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>サーバーを叩いて結果を出力するだけです。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>サーバーを作る前はgoで<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>直接叩いてやっていたのを、<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>化と共にtypescriptとかそう言うのに置き換えようと思ったんですが、断念して今はgoで<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を叩いてやっていっています。</p> <h1>おわりに</h1> <p>このプロジェクトは、いつでもみなさんの協力をお待ちしております。最近めちゃくちゃコントリビューターが増えて、嬉しい。 普通の<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>よりは競プロ勢の人にとってとっつきやすいと思うので、pull requestsとかに興味があるけど、難しそうだし〜と言う人は、ぜひ!</p> <p>明日(概念崩壊)はsaka_ponさんの<a href="https://sakapon.wordpress.com/2019/12/15/competitive-short-code/">競技プログラミングでも C# で簡潔に書きたい</a> です。ありがとうございました。</p> <div class="footnote"> <p class="footnote"><a href="#fn-071a6acf" name="f-071a6acf" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">ざっくり言うと、自動で「ライブラリをちょっと修正するたびに全部の問題に投げ直す」ことが出来ます</span></p> </div> yosupo yukicoder No.940 ワープ ε=ε=ε=ε=ε=│;p>д<│ hatenablog://entry/26006613475825263 2019-12-03T23:41:49+09:00 2019-12-03T23:41:49+09:00 基本方針 ガン見 maspypy.com 解法 最近ipad買いました(私事) goodnote <h2>基本方針</h2> <p>ガン見</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fmaspypy.com%2F" title="maspyのHP" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://maspypy.com/">maspypy.com</a></cite></p> <h2>解法</h2> <p>最近<a class="keyword" href="http://d.hatena.ne.jp/keyword/ipad">ipad</a>買いました(私事)</p> <p><figure class="figure-image figure-image-fotolife" title="goodnote"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20191203/20191203234110.jpg" alt="f:id:yosupo:20191203234110j:plain" title="f:id:yosupo:20191203234110j:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>goodnote</figcaption></figure></p> yosupo Static Range Union Find hatenablog://entry/26006613464718530 2019-11-12T00:15:35+09:00 2019-11-12T00:16:03+09:00 N頂点のUnion Findが与えられます。以下のクエリがQ個与えられます。 given l, r, dist: merge(l, r), merge(l+1, r+1), merge(l+2, r+2), ..., merge(l+dist, r+dist) これを処理した後のUnion Findを計算してください これは D: LCP(prefix,suffix) - 「みんなのプロコン 2018」決勝 | AtCoder の本質部分です(ネタバレ) O(N α(N) + Q)で解けます。 Submission #8391439 - 「みんなのプロコン 2018」決勝 | AtCoder <p>N頂点のUnion Findが与えられます。以下のクエリがQ個与えられます。</p> <ul> <li>given l, r, dist: merge(l, r), merge(l+1, r+1), merge(l+2, r+2), ..., merge(l+dist, r+dist)</li> </ul> <p>これを処理した後のUnion Findを計算してください</p> <p>これは <a href="https://yahoo-procon2018-final.contest.atcoder.jp/tasks/yahoo_procon2018_final_d">D: LCP(prefix,suffix) - &#x300C;&#x307F;&#x3093;&#x306A;&#x306E;&#x30D7;&#x30ED;&#x30B3;&#x30F3; 2018&#x300D;&#x6C7A;&#x52DD; | AtCoder</a> の本質部分です(ネタバレ)</p> <p>O(N α(N) + Q)で解けます。</p> <p><a href="https://yahoo-procon2018-final.contest.atcoder.jp/submissions/8391439">Submission #8391439 - &#x300C;&#x307F;&#x3093;&#x306A;&#x306E;&#x30D7;&#x30ED;&#x30B3;&#x30F3; 2018&#x300D;&#x6C7A;&#x52DD; | AtCoder</a></p> yosupo TTPC2019参加記 hatenablog://entry/26006613415277352 2019-09-04T01:11:11+09:00 2019-09-04T01:11:11+09:00 TTPC2019にチームyosupo(yosupo, yosupo, yosupo)で参加して、優勝しました 前日 ここいる?*1 うんち 当日 コンテスト前 無(二度寝したので) 昼食 無(二度寝したので) チーム決め 無(それはそう) コンテスト 東 京 工 業 大 学 と4年ぶりの再会 A TTPC2020 たのしみ~(問題設定無視) B D言語なら正規表現があるんですよね→デバッグ出力、消し忘れ… C 解けないから飛ばした D 実験書こうとしたが、そういえば素数って奇数だったなって 素朴で好き E 000 111 222 からちょっといじるとなんか出来た F 最小費用流を貼ってグラフを… <p>TTPC2019にチームyosupo(yosupo, yosupo, yosupo)で参加して、優勝しました</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20190904/20190904003306.jpg" alt="f:id:yosupo:20190904003306j:plain" title="f:id:yosupo:20190904003306j:plain" class="hatena-fotolife" itemprop="image"></span></p> <h1>前日</h1> <p>ここいる?<a href="#f-cfb88744" name="fn-cfb88744" title="https://kenkoooo.hatenablog.com/entry/2019/08/31/084809">*1</a></p> <p><figure class="figure-image figure-image-fotolife" title="うんち"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20190901/20190901232411.png" alt="f:id:yosupo:20190901232411p:plain" title="f:id:yosupo:20190901232411p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>うんち</figcaption></figure></p> <h1>当日</h1> <h2>コンテスト前</h2> <p>無(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%F3%C5%D9%BF%B2">二度寝</a>したので)</p> <h2>昼食</h2> <p>無(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%F3%C5%D9%BF%B2">二度寝</a>したので)</p> <h2>チーム決め</h2> <p>無(それはそう)</p> <h1>コンテスト</h1> <p>東 京 工 業 大 学 と4年ぶりの再会</p> <h2>A</h2> <p>TTPC2020 たのしみ~(問題設定無視)</p> <h2>B</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/D%B8%C0%B8%EC">D言語</a>なら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>があるんですよね→<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>出力、消し忘れ…</p> <h2>C</h2> <p>解けないから飛ばした</p> <h2>D</h2> <p>実験書こうとしたが、そういえば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%C7%BF%F4">素数</a>って奇数だったなって</p> <p>素朴で好き</p> <h2>E</h2> <pre class="code" data-lang="" data-unlink>000 111 222</pre> <p>からちょっといじるとなんか出来た</p> <h2>F</h2> <p>最小費用流を貼ってグラフを構築しようとして、何かがおかしいことに気づいた</p> <h2>M</h2> <p>UKUNICHIAがFAしてるから開いた</p> <p>抽象化ライブラリじゃ処理できないけど俺の全方位木DP実装力を見せてやるぜ→<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>、42分…</p> <h2>L</h2> <p>初感が(x * 100,000 + y)で分けたら良さそうだなぁだったから対して苦労しなかった 天才かも</p> <p>半分全列挙だけど見たことない気がする</p> <h2>G</h2> <p>丁寧に場合分けをやった</p> <h2>O</h2> <p>とりあえず2冪から考えてみるか→普通に出来た</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20190904/20190904004546.png" alt="f:id:yosupo:20190904004546p:plain" title="f:id:yosupo:20190904004546p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>BDDって31 * 31なんて処理できたっけ?って思ってたけど、経路数が少ないから(別の方法で)なんとかなるんですね(FPTじゃん)</p> <h2>C</h2> <p>再考したら普通に解けた</p> <h2>H</h2> <p>俺の平衡二分木ライブラリを見せてやるぜ→1年ぐらい使ってなかったため、使用法を完全に忘却…</p> <p>動的Fenwick Treeで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%AA%C3%E3%A4%F2%C2%F9%A4%B9">お茶を濁す</a></p> <h2>I</h2> <p>とりあえずQを素べきに分解して考えてたが、実は分解しないほうが見通しが良かった? 細かいところを詰めずに実装を開始したら大変なことになった</p> <h2>J</h2> <p>Iの合間に解いてたので、パパっと実装</p> <h2>K</h2> <p>とりあえず手でケースを試しまくるとハミング距離(=<a class="keyword" href="http://d.hatena.ne.jp/keyword/FFT">FFT</a>)が関係しそうなことがわかって、N=100,000 / 2.5sはいかにも<a class="keyword" href="http://d.hatena.ne.jp/keyword/FFT">FFT</a>っぽい制約なので確信(は?)</p> <p>とりあえず2種類のハミング距離を眺めて、大小関係がサンプルと一致したので提出…(競プロをやめろ)</p> <p><code>MuriyarokonnNaN</code>と4年ぶりの再会</p> <h2>N</h2> <p>問題設定がぶっ飛んでて好き とりあえず適当なのを投げたら全然ダメだった</p> <h1>懇親会</h1> <p>🍣が出てきて、嬉C</p> <p>自分より若い人が大半で、もう気づいたら高齢者…(ホロリ)</p> <p>話したことない人と喋ったので、満点!(コミュ障)</p> <h1>感想</h1> <p>久しぶりの5時間コンテストで楽しかったです。運営のみなさんありがとうございました。</p> <p><s><a class="keyword" href="http://d.hatena.ne.jp/keyword/AtCoder">AtCoder</a>のせいで</s>データ構造ライブラリが化石になってるんでちゃんと整備しなおさなきゃ…</p> <div class="footnote"> <p class="footnote"><a href="#fn-cfb88744" name="f-cfb88744" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://kenkoooo.hatenablog.com/entry/2019/08/31/084809">https://kenkoooo.hatenablog.com/entry/2019/08/31/084809</a></span></p> </div> yosupo プロキシを建てた hatenablog://entry/17680117127216460898 2019-07-11T00:41:41+09:00 2019-07-11T00:41:41+09:00 背景 マンションのネットが、マンション共有で安い!みたいなやつ なんかネットが不安定だった 最近、不安定を再現する方法が分かった(研で使っているサイボウズをノートパソコンから開く)ので、真面目に調べることにした 原因 共有部分のルーターのIPマスカレード制限(多分) 要するに一度にめっちゃたくさんのサイトとか画像とかにアクセスすると死ぬってやつで、サイボウズは細かい画像が多い & http1.1 で死んでいたっぽい 対策 内側と外側にプロキシを建てた(内側: Raspberry Pi / 外側: GCP) これが こう 建て方 めんどそうだなぁと思ったけど、GitHub - ginuerzh/… <h1>背景</h1> <p>マンションのネットが、マンション共有で安い!みたいなやつ</p> <p>なんかネットが不安定だった</p> <p>最近、不安定を再現する方法が分かった(研で使っている<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%A4%A5%DC%A5%A6%A5%BA">サイボウズ</a>をノートパソコンから開く)ので、真面目に調べることにした</p> <h1>原因</h1> <p>共有部分の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/IP%A5%DE%A5%B9%A5%AB%A5%EC%A1%BC%A5%C9">IPマスカレード</a>制限(多分)</p> <p>要するに一度にめっちゃたくさんのサイトとか画像とかにアクセスすると死ぬってやつで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B5%A5%A4%A5%DC%A5%A6%A5%BA">サイボウズ</a>は細かい画像が多い &amp; http1.1 で死んでいたっぽい</p> <h1>対策</h1> <p>内側と外側にプロキシを建てた(内側: <a class="keyword" href="http://d.hatena.ne.jp/keyword/Raspberry%20Pi">Raspberry Pi</a> / 外側: <a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>)</p> <p><figure class="figure-image figure-image-fotolife" title="これが"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20190711/20190711002838.png" alt="f:id:yosupo:20190711002838p:plain" title="f:id:yosupo:20190711002838p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>これが</figcaption></figure></p> <p><figure class="figure-image figure-image-fotolife" title="こう"><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/y/yosupo/20190711/20190711002853.png" alt="f:id:yosupo:20190711002853p:plain" title="f:id:yosupo:20190711002853p:plain" class="hatena-fotolife" itemprop="image"></span><figcaption>こう</figcaption></figure></p> <h1>建て方</h1> <p>めんどそうだなぁと思ったけど、<a href="https://github.com/ginuerzh/gost">GitHub - ginuerzh/gost: GO Simple Tunnel - a simple tunnel written in golang</a> を使ったら一瞬で立った すごい</p> <p>client(raspi)</p> <pre class="code" data-lang="" data-unlink>./gost -L http://:1080 -F http+mws://yosupo:$PASSWORD@$SERVER_IP:1080</pre> <p>server(<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>)</p> <pre class="code" data-lang="" data-unlink>./gost -L http+mws://yosupo:$PASSWORD@:1080</pre> <h1>難点</h1> <p>http / <a class="keyword" href="http://d.hatena.ne.jp/keyword/https">https</a>プロキシの設定が必要</p> <ul> <li>なんか自動設定できるらしいが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/DHCP">DHCP</a>サーバーを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EB%A1%BC%A5%BF%A1%BC">ルーター</a>のじゃなくて自前で用意しないといけなそうで、面倒…</li> </ul> <p>お金がかかる</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/GCP">GCP</a>のサーバー自体は他の用途にも使っているものだからいいんだけど、ネットワーク(下り)に料金がかかる</li> </ul> yosupo Gifted Infantsのチーム戦略について hatenablog://entry/17680117127212035940 2019-07-02T12:24:33+09:00 2019-07-02T16:29:44+09:00 メンバー yosupo, sigma, maroon 戦略 明確な戦略は特になかったです(完)。 いくつかのルールを守りながら(守らないこともある)、毎回適当にやってた感じです ルール 今誰がどの問題を読んだか、考えたか の紙を作る 特に得意ではないジャンルを無理にやらない 例(yosupo: DP, 109 + 7, sigma: JOI, maroon: AOJ-ICPC) 解いた後でも苦手な実装だったら人に投げる ちゃんと実装を詰める ちゃんと声かけをする 必要なら厳しい言葉をためらわない 人がある程度燃えたら他の人が強制的に介入する 実装が重かったら相談する(想定よりはるかに重い実装方… <h1>メンバー</h1> <p>yosupo, <a class="keyword" href="http://d.hatena.ne.jp/keyword/sigma">sigma</a>, maroon</p> <h1>戦略</h1> <p>明確な戦略は特になかったです(完)。</p> <p>いくつかのルールを守りながら(守らないこともある)、毎回適当にやってた感じです</p> <h3>ルール</h3> <ul> <li>今誰がどの問題を読んだか、考えたか の紙を作る</li> <li>特に得意ではないジャンルを無理にやらない 例(yosupo: DP, 10<sup>9</sup> + 7, <a class="keyword" href="http://d.hatena.ne.jp/keyword/sigma">sigma</a>: JOI, maroon: AOJ-<a class="keyword" href="http://d.hatena.ne.jp/keyword/ICPC">ICPC</a>)</li> <li>解いた後でも苦手な実装だったら人に投げる</li> <li>ちゃんと実装を詰める</li> <li>ちゃんと声かけをする</li> <li>必要なら厳しい言葉をためらわない</li> <li>人がある程度燃えたら他の人が強制的に介入する</li> <li>実装が重かったら相談する(想定よりはるかに重い実装方針を考えていて、相談すると綺麗になるってことが結構ある)</li> <li>解法が 難しい / 未証明 / 貪欲 / 計算量が怪しい などの場合は相談する</li> <li>有名問題っぽい見た目になったら他の二人に知ってないか聞く</li> </ul> <p>あたりかな 明文化されていたわけではないです。とにかく相談を沢山するチームだったと思います。</p> <h1>練習セット</h1> <p>5時間セットをやる→反省する を繰り返したぐらいで、特に特別なことはしてないです。ちゃんと動き方とかの反省をするといいかもしれません。</p> <h3>Petrozavodsk Camp(9)</h3> <ul> <li>セット数: 11日9セット(oooxoooxooo)</li> <li>難易度: [<a class="keyword" href="http://d.hatena.ne.jp/keyword/ICPC">ICPC</a>-Japan, INF]</li> <li>開催地: Petrozavodsk, オンライン(有料)</li> <li>注意点: 凍死</li> </ul> <h3>MIPT Camp(6)</h3> <ul> <li>セット数: 7日6セット(oooxooo)</li> <li>難易度: Petrozavodskと同じかちょっと簡単</li> <li>開催地: MIPT, 同時開催で世界のどっか(年による)</li> <li>注意点: パスポートを忘れない</li> </ul> <h3>Opencup(11)</h3> <ul> <li>セット数: 15 ~ 20セットぐらい</li> <li>難易度: [<a class="keyword" href="http://d.hatena.ne.jp/keyword/ICPC">ICPC</a>-Japan, INF], 時々ゴミ</li> <li>開催地: オンライン(無料)</li> <li>注意点: 上2つのキャンプと同じ問題セットが使われることがある, 終了が遅い</li> </ul> <p>知っての通り別にOpenじゃないです</p> <h3>Ejudge(15)</h3> <ul> <li>セット数: たくさん</li> <li>難易度: 様々</li> <li>開催地: オンライン</li> </ul> <p>キャンプとかOpencupの過去問が詰まったジャッジです。</p> <p>これもOpenじゃないです</p> <h3>その他(13)</h3> <p>コドフォのGymとかRUPCとか夏合宿とか</p> <h1>ライブラリ</h1> <p>ライブラリにファイル容量制限とかがあって、それを超えて大変だったりした(html + @printerで作ってたら、印刷するOSによって容量が変わった なぜ?)</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.dropbox.com%2Fs%2F1zxohqaxrb87uft%2FGifted_Infants_The_University_of_Tokyo___erated_files-job_14.pdf%3Fdl%3D0" title="Gifted_Infants_The_University_of_Tokyo___erated_files-job_14.pdf" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://www.dropbox.com/s/1zxohqaxrb87uft/Gifted_Infants_The_University_of_Tokyo___erated_files-job_14.pdf?dl=0">www.dropbox.com</a></cite></p> <p>単体法 出典: <a href="https://github.com/koosaga/DeobureoMinkyuParty">GitHub - koosaga/DeobureoMinkyuParty: &#xB7ED;&#xC2A4;&#xB97C; &#xB7ED;&#xC2A4;&#xB2F5;&#xAC8C; &#xB4E0;&#xB4E0;&#xD55C; &#xC5F0;&#xC2B5;&#xD5EC;&#xD31F; &#xB354;&#xBD88;&#xC5B4;&#xBBFC;&#xADDC;&#xB2F9;</a></p> <p><span style="color: #d32f2f">少なくとも</span><a class="keyword" href="http://d.hatena.ne.jp/keyword/%B9%D4%CE%F3%BC%B0">行列式</a>がバグっています</p> <h1>環境</h1> <p>ある程度は本番に環境を合わせて練習したつもりです。 突然環境が変わると上手くいかなくて悲しい気持ちになりがち(経験則)</p> <p>個人的には キーボード >>>> エディタ >= OS >>>> その他 ぐらいの重要度</p> <h3>キーボード</h3> <ul> <li>英字配列以外の選択肢はないです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%FC%CB%DC%B8%EC%C7%DB%CE%F3">日本語配列</a>は今すぐやめましょう。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DD%A5%EB%A5%C8%A5%AC%A5%EB">ポルトガル</a>配列はやめろ</li> <li>高速にタイピング出来る人がいないとライブラリ写経が絶望的になる</li> </ul> <h3>OS</h3> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/Ubuntu">Ubuntu</a>以外の選択肢はないです(フラグ)。バージョンは最新のLTSでよさそう</li> <li>yokohamaはほとんどデフォルト設定だと思いました。WFではなんか見た目とかが弄られていました。</li> </ul> <h3>エディタ</h3> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/visual%20studio">visual studio</a> / <a class="keyword" href="http://d.hatena.ne.jp/keyword/atom">atom</a> が使えないことに注意。本番で使えるエディタ一覧はサイトから見れます</li> <li>色々設定をする場合、それだけ時間を失うことに注意する必要があります</li> <li>理想的には3人でエディタ, 設定を合わせることですが、あまり強制しようとすると喧嘩になります。</li> </ul> <p>Gifted Infantsは</p> <ul> <li>maroon: gedit</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/sigma">sigma</a>: CLion</li> <li>yosupo: CLion</li> <li>CLionの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%D0%A5%A4%A5%F3%A5%C9">キーバインド</a>: <a class="keyword" href="http://d.hatena.ne.jp/keyword/emacs">emacs</a> + (ctrl+c, ctrl+v, ctrl+z)=(copy, paste, undo)</li> </ul> <p>でした。カオス。CLionはデフォルトで色々上手く動くのでいいです。でもノーパソだと発熱がすごい。</p> <h3>プリンター</h3> <p>印刷のタイムラグを表現するために、「ファイルをアップロードして1分後にダウンロード出来るようになる」サイトを作って使っていました。あんまり意味なかった気がする</p> <h1>おすすめ</h1> <h3>CapsをCtrlに</h3> <p><code>setxkbmap -option ctrl:nocaps</code> で出来ます</p> <h3>ターミナルを簡単にリセット出来るように</h3> <p><code>alias c='tput reset'</code></p> <h3><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>オプション</h3> <p><code>-Wall -Wextra -Wshadow -DGLIBCXX_DEBUG -ftrapv</code> を使う</p> <h3>check.sh</h3> <p>参考: <a href="https://kimiyuki.net/blog/2015/09/25/competitive-programming-coding/">&#x7AF6;&#x6280;&#x30D7;&#x30ED;&#x30B0;&#x30E9;&#x30DF;&#x30F3;&#x30B0;&#x3067;&#x30B3;&#x30FC;&#x30C7;&#x30A3;&#x30F3;&#x30B0;&#x306E;&#x969B;&#x6C17;&#x3092;&#x4ED8;&#x3051;&#x3066;&#x3044;&#x308B;&#x3053;&#x3068; - &#x3046;&#x3055;&#x304E;&#x5C0F;&#x5C4B;</a></p> <pre class="code" data-lang="" data-unlink>make $1 for f in tests/*$1*.in; do echo &#39;#### Start &#39; $f ./$1 &lt; $f done</pre> <p>みたいな、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B3%A5%F3%A5%D1%A5%A4%A5%EB">コンパイル</a>してサンプルを一括で全部試せる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B7%A5%A7%A5%EB%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">シェルスクリプト</a>を用意すると便利です。</p> yosupo Google hash code 2019 Final hatenablog://entry/17680117127107990780 2019-05-03T22:08:16+09:00 2019-05-03T22:08:16+09:00 Google hash code 2019に参加してきました hash codeって? google主催のコンテストで、GCJみたいなものです。一番の違いは形式で、2-4人チーム、パソコン無制限で短時間(予選:4時間, 本戦:5時間)というコンテストになっています。 予選は一発で、上位から、人数の総和が150人ぐらいになるまで進出らしいです。 ということからわかるように、GCJ Finalなどのtop25コンテストよりはるかに進出しやすいコンテストな気がします。 交通費 交通費、ホテル、食費のうちいくらか出すとサイトには書いてあります。 僕らがもらう予定の交通費は、全額ではないけど大半、ぐらい… <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Google">Google</a> hash code 2019に参加してきました</p> <h2>hash codeって?</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/google">google</a>主催のコンテストで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCJ">GCJ</a>みたいなものです。一番の違いは形式で、2-4人チーム、パソコン無制限で短時間(予選:4時間, 本戦:5時間)というコンテストになっています。 予選は一発で、上位から、人数の総和が150人ぐらいになるまで進出らしいです。</p> <p>ということからわかるように、<a class="keyword" href="http://d.hatena.ne.jp/keyword/GCJ">GCJ</a> Finalなどのtop25コンテストよりはるかに進出しやすいコンテストな気がします。</p> <h2>交通費</h2> <p>交通費、ホテル、食費のうち<em>いくらか</em>出すとサイトには書いてあります。 僕らがもらう予定の交通費は、全額ではないけど大半、ぐらいです。詳しく知りたい人は直接聞いてください。</p> <h2>環境</h2> <p>ファイルの共有は<a class="keyword" href="http://d.hatena.ne.jp/keyword/dropbox">dropbox</a>を使用しました。短時間なのでgitの旨味がほとんどないからです。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%A3%A5%EC%A5%AF%A5%C8">ディレクト</a>リは</p> <pre class="code" data-lang="" data-unlink>- common・ - common.h - random.h - yosupo/ - main.cpp - sigma/ - A.cpp - sugim/ - A.cpp - maroon/ - A/ - main.cpp</pre> <p>みたいな感じです。common.hには問題のファイルからの入力、出力、スコア計算などを書き並べ、全員importします。</p> <p>戦略みたいなのはなくて、分担したり協力したりをぬるっとやりました。</p> <h2>結果</h2> <p>🌞</p> <h2>感想</h2> <p>5時間で4人4台チームコンテストすると、もうめちゃくちゃになります(チーム戦とは?)</p> <p>事前に誰が何をやるかとか、もはや完全に分割して別のケースを解くとかするといい気がします。</p> <p>あとpast gloryはしっかり優勝してて、さすがだなあって思いました。</p> <h2>余談1</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Mac">Mac</a> book pro(2018)を使っているんですが、最近いろんなキーが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C1%A5%E3%A5%BF%A5%EA%A5%F3%A5%B0">チャタリング</a>を開始して、本戦がしんどかったです。 space, nあたりが酷い、対策されたって聞いたのに…</p> <h2>余談2</h2> <p>GWだったのでダブリン->イタリア->ドイツの大型旅行にして、今イタリアにいるんですが、パスポート失くしました。</p> yosupo DDCC 2019 hatenablog://entry/10257846132709895447 2019-01-20T08:48:59+09:00 2019-01-20T08:52:02+09:00 コード部門 A: なんかバグった、素直にD言語のgroup関数を使うべきだったかも、遅かった B: 平衡二分木使うか悩んだけどしばらく考えたら貪欲で普通に解けた、遅かった D: もう典型、segtree.hとmatrix.hを貼って終わり、速かった C: みんな解法は簡単というけどそんなことない気がする、実装の方は2ヶ月後にICPC WF行く人幾何担当がこれを解けないとチームをクビになる、でも遅かった(は?) E: コンテスト開始からチマチマ考えていた、1080までは見えていて、残り時間との兼ね合いでそこで諦めることにした、見積もりを間違えてて1110が取れた、速かった DISCOの人が順位表… <h2>コード部門</h2> <ul> <li>A: なんかバグった、素直に<a class="keyword" href="http://d.hatena.ne.jp/keyword/D%B8%C0%B8%EC">D言語</a>のgroup関数を使うべきだったかも、遅かった</li> <li>B: 平衡二分木使うか悩んだけどしばらく考えたら貪欲で普通に解けた、遅かった</li> <li>D: もう典型、segtree.hとmatrix.hを貼って終わり、速かった</li> <li>C: みんな解法は簡単というけどそんなことない気がする、実装の方は2ヶ月後に<a class="keyword" href="http://d.hatena.ne.jp/keyword/ICPC">ICPC</a> WF行く人幾何担当がこれを解けないとチームをクビになる、でも遅かった(は?)</li> <li>E: コンテスト開始からチマチマ考えていた、1080までは見えていて、残り時間との兼ね合いでそこで諦めることにした、見積もりを間違えてて1110が取れた、速かった</li> </ul> <p>DISCOの人が順位表の凍結にこだわっていそうだったので、せっかくだからすっとぼけることにした。</p> <p>昼食の時はCが大変なことになって終わった(終わったとは当然文字通り問題が終わった=ACという意味です)とか、Eも提出だけはした(提出した結果0点だったとは言っていない)とか言ってた</p> <h2>装置実装部門</h2> <p>予選: なぜか壮大なものを実装しようとしていたが、そもそも正の点数を取ることすら相当難しいと気づいて方針転換。これが幸いしてギリギリ滑り込めた</p> <p>10位に入れていそうだったので、予選が終わった後の休憩時間ではすぐに本戦のためにプログラムを改善開始</p> <p>本戦はesper力、観察力、実装力、盤外戦術力みたいなのが全部問われる不思議なコンテストだった</p> <p>pre本戦:</p> <ul> <li>向こうの口ぶり的に最速で移動するとどうせ大変なことになる設定なんだろう</li> <li>最高速を減らすことのメリットが少なすぎる、いじるべきは加速減速時間のみでいいだろう</li> <li>普通に実装すると45度移動になるが、水をこぼさないように運ぶなら明らかに直線的に動くべきだろう</li> <li>AとかBに移動するとき、真下まで行かずにちょっと前で止まると少しだけお得</li> <li>pre本戦は自分の挙動を確認する以上に、他人の挙動を確認するのが大事だろう</li> <li>submissionの点数を見ることで、n週ギリギリの調整が出来る</li> </ul> <p>あたりには気づいていて、これらを踏まえて適当に良さそうなのを実装して、pre本戦は紙とペンを持って気づいたことをなんでもメモを取ることにした</p> <p>また、pre本戦ではわざとダメなの送って気づいたことを秘匿しようかとも思ったけど、ぶっつけ本戦で挙動が終わるリスクを考えたら素直に送った方がいいと判断した</p> <p>以下はpre本戦中の考察や、人を見て気づいたこと</p> <ul> <li>E -> Aは水がないんだから最速で移動すればいい(また、A -> Bは水が少ないから少し早くてもいい)</li> <li>AとかBに移動するとき、真下じゃなくてちょっと前で止まると少しだけお得だが、ここでギリギリを攻めると水がうまく給水できずに零れる</li> <li>4週しかない、5週は時間的にまず不可能だろうし、3週だと水が少なすぎる気がする</li> <li>pre本戦でもっと情報を収集するべきだった(sugimは各週ごとに速度を変えて水のこぼれ具合を調べていた)</li> </ul> <p>また、自分のコードはほとんど自分の予想通りに動いていることが確認できて嬉しかった、特に直線的に動けているかは不安だったので</p> <p>これを踏まえて、その後の実装タイムでは</p> <ul> <li>まずは、E -> Aの速度をmaxにし、A -> Bの加速度はB -> Eの2倍にすることにした(水量が半分だから)</li> <li>5週でまともな量は運べないだろうこと、3週だとどれだけ上手くやっても4週に勝てないだろうことを確認</li> <li>理論値480では勝てないだろうと予想して、理論値と加速度のバランスを雰囲気で調整していくことに</li> <li>↑pre本戦一位(自分)が水をかなりこぼしていたのに435であったことを加味すると、450 ~ 460程度は平気で出るだろうと予想した</li> <li>また、ここらへんでA -> Bの加速度が2倍ではお話にならないことに気づく(ビーカーの形的に水量が半分でも水位は結構高くなる)、謎の仮定(めちゃくちゃでした)を置いた計算の結果1.43(1 / 0.7)倍にすることにした</li> <li>理論値528で挑むことにした、理由は特になくて、注ぐ時間を100ms刻みで選ぶと480, 504, 528, 552になるんだけど、504だと流石にしんどいだろう &amp; 552は欲張りすぎだろうみたいな判断から)</li> </ul> <p>結果は予想より全然水がこぼれず500.5になった、preよりこぼした量が減ったというのは驚きで、多分もっと理想の理論値は上だったっぽい(下手したら552まで行くかも)</p> yosupo AGC 029 hatenablog://entry/10257846132685420839 2018-12-16T01:10:50+09:00 2018-12-16T01:10:50+09:00 A 隣り合う要素をswapして数列をsortする最小回数は転倒数と呼ばれています B 2で割れる回数でグループ分けして←いらない 大きい順にマッチしていく という解法で解いた、1個目いらない 証明 xとマッチできる値たちのうち、x以下のものは1種類しかない、というのが本質 最大の値xとマッチできるyが存在しなかったらx消していい 存在したらx - yでマッチ作っていい(x - yを使わないマッチングがあったとしても、x - yを使うマッチングに変換できる、なぜならx - yを使わないならxはぼっちで、yはx以外の何かとマッチしている、よってy - 何かをばらしてx - yを足せばいい) C そ… <h2>A</h2> <p>隣り合う要素をswapして数列をsortする最小回数は転倒数と呼ばれています</p> <h2>B</h2> <ul> <li>2で割れる回数でグループ分けして←いらない</li> <li>大きい順にマッチしていく</li> </ul> <p>という解法で解いた、1個目いらない</p> <h4>証明</h4> <p>xとマッチできる値たちのうち、x以下のものは1種類しかない、というのが本質</p> <p>最大の値xとマッチできるyが存在しなかったらx消していい</p> <p>存在したらx - yでマッチ作っていい(x - yを使わないマッチングがあったとしても、x - yを使うマッチングに変換できる、なぜならx - yを使わないならxはぼっちで、yはx以外の何かとマッチしている、よってy - 何かをばらしてx - yを足せばいい)</p> <h2>C</h2> <p>そもそもどういう文字列をつくっていけばいいか考える</p> <p>二分探索をする</p> <p>シミュレーション</p> <h2>D</h2> <h2>E</h2> <p>実装を詰めなかったため、破滅</p> <h2>F</h2> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/twitter">twitter</a></p> yosupo CODE FESTIVAL 2018 hatenablog://entry/10257846132671812278 2018-11-18T13:49:08+09:00 2018-11-18T13:49:08+09:00 本選 対策 特になし 結果 ふつう 原因 Hの得意度は参加者の中でトップクラスだった自信があるため,Iを速やかに解ければ勝っていたと思うが,Iに90分掛けたためどうしようもないね 対策 転生? りんごの挑戦状 対策 ペイントで様々な色を作って眺める トップページを暗記する,日時を重点的に 結果 どうしてこんなことに 原因 R:G:Bの比だけを注意していて,明るさという概念を忘れていた。|R-r| + |G - g| + |B - b|ならR:G:Bの比は本質じゃないんだよな 対策 そもそも地理系に弱いので詰んでいませんか?詰んでいますね(チーム戦にしませんか?) リレー 対策 例年はふわっとや… <h1>本選</h1> <h4>対策</h4> <p>特になし</p> <h4>結果</h4> <p>ふつう</p> <h4>原因</h4> <p>Hの得意度は参加者の中でトップクラスだった自信があるため,Iを速やかに解ければ勝っていたと思うが,Iに90分掛けたためどうしようもないね</p> <h4>対策</h4> <p>転生?</p> <h1>りんごの挑戦状</h1> <h4>対策</h4> <ul> <li>ペイントで様々な色を作って眺める</li> <li>トップページを暗記する,日時を重点的に</li> </ul> <h4>結果</h4> <p>どうしてこんなことに</p> <h4>原因</h4> <p>R:G:Bの比だけを注意していて,明るさという概念を忘れていた。|R-r| + |G - g| + |B - b|ならR:G:Bの比は本質じゃないんだよな</p> <h4>対策</h4> <p>そもそも地理系に弱いので詰んでいませんか?詰んでいますね(チーム戦にしませんか?)</p> <h1>リレー</h1> <h4>対策</h4> <p>例年はふわっとやってふわっと終わるみたいな戦略だったけど,今年は真面目に</p> <p>最初の5問に2人 * 5で取り掛かって後の5問は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CE%D7%B5%A1%B1%FE%CA%D1">臨機応変</a>にみたいな(いややっぱりふわっとしていない?)</p> <h4>結果</h4> <p>2位</p> <h4>原因</h4> <p>戦略が上手く行った,とかではなく他のチームの話を聞くとそもそもチームメンバーが優秀だったっぽい(完)</p> <h4>対策</h4> <p>文句なし</p> yosupo minimize しんどさ s.t. 早起き hatenablog://entry/10257846132655941317 2018-10-18T15:51:59+09:00 2018-10-19T17:16:03+09:00 背景 突然寒くなって二度寝不可避 →出来る限りしんどくなく早起きしたい 目的関数 minimize しんどさ s.t. 早起き, 現実的な予算 10時起き安定を目標にする 環境 照明 空き巣対策に決まった時間に勝手に電気がつく機能があるのでオンタイマーとして使用,30分ぐらい前につくようになってればいい? 温度 エアコンのオンタイマーを使用,指定方が絶対時間じゃなくて相対時間なのでめんどい。寝起きは体が冷えているのでちょっと温度高めがいいかもしれない。 寝起き直後の行動 布団でスマホを開くと9割がた二度寝なので対策を行う必要がある。 布団から遠くにスマホをおいておく,アプリで目覚ましを止めたあ… <h1>背景</h1> <p>突然寒くなって<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%F3%C5%D9%BF%B2">二度寝</a>不可避 →出来る限りしんどくなく早起きしたい</p> <h1>目的関数</h1> <p>minimize しんどさ s.t. 早起き, 現実的な予算</p> <p>10時起き安定を目標にする</p> <h1>環境</h1> <h4>照明</h4> <p>空き巣対策に決まった時間に勝手に電気がつく機能があるのでオンタイマーとして使用,30分ぐらい前につくようになってればいい?</p> <h4>温度</h4> <p>エアコンのオンタイマーを使用,指定方が絶対時間じゃなくて相対時間なのでめんどい。寝起きは体が冷えているのでちょっと温度高めがいいかもしれない。</p> <h1>寝起き直後の行動</h1> <p>布団で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>を開くと9割がた<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C6%F3%C5%D9%BF%B2">二度寝</a>なので対策を行う必要がある。 布団から遠くに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>をおいておく,アプリで目覚ましを止めたあとしばらくは<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%DE%A5%DB">スマホ</a>にロックをかける?</p> <h1>食事</h1> <h4>飲み物</h4> <p>朝起きるといえばコーヒーだが,そこまで好きではない。 紅茶(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C6%A5%A3%A1%BC">ティー</a>バッグ)が最善だろうか</p> <h4>食べ物</h4> <p>食パンはポロポロこぼれるので苦手,レーズンロールを夜のうちに準備しておくとかだろうか,深夜に食べてしまわない強靭な精神力が必要</p> <h1>起きたあとの目的</h1> <p>これがあるかないかで起床成功確率は大きく違う</p> <h4>授業/ゼミ</h4> <p>そもそも午前に無い</p> <h4>研究</h4> <p>最もだが,寝起きでやってなにか進むものではない</p> <h4>散歩</h4> <p>寒い</p> <h4>ばちゃ</h4> <p>一番ありかも,そもそも起床の目的とも合致している</p> <h1>気づき</h1> <p>着替え</p> yosupo C++ライブラリのテストを切り出している hatenablog://entry/10257846132655740121 2018-10-18T00:42:54+09:00 2018-10-18T00:42:54+09:00 C++ライブラリのストレステストを切り出しているという話 背景 プログラミングコンテストのライブラリには色々と要求されるが,一番重要なのはバグを少なくすることだと思う。 バグが無いだろうと信頼できればバグったときでもライブラリ以外の部分だけ疑えば良くなり,逆にライブラリを疑いだしたらもう実装を簡略化, 高速化するという目的に対し本末転倒ではないかと思う。 もちろんバグがないことを保証するのは不可能であるが,適切にテストを行えばバグが潜む可能性を十分下げることは可能だと思う。 テストの書き方 大きく分けて オンラインジャッジを活用する / リポジトリにテスト用のコードも書く の2種があると思う,… <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>ライブラリのストレステストを切り出しているという話</p> <h1>背景</h1> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0%A5%B3%A5%F3%A5%C6%A5%B9%A5%C8">プログラミングコンテスト</a>のライブラリには色々と要求されるが,一番重要なのはバグを少なくすることだと思う。 バグが無いだろうと信頼できればバグったときでもライブラリ以外の部分だけ疑えば良くなり,逆にライブラリを疑いだしたらもう実装を簡略化, 高速化するという目的に対し本末転倒ではないかと思う。</p> <p>もちろんバグがないことを保証するのは不可能であるが,適切にテストを行えばバグが潜む可能性を十分下げることは可能だと思う。</p> <h1>テストの書き方</h1> <p>大きく分けて オンラインジャッジを活用する / <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>にテスト用のコードも書く の2種があると思う,両方一長一短</p> <p>僕は後者の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%DD%A5%B8%A5%C8%A5%EA">リポジトリ</a>にテスト用のコードも書く,のほうが好みで,自分の<a class="keyword" href="http://d.hatena.ne.jp/keyword/C%2B%2B">C++</a>ライブラリ(<a href="https://github.com/yosupo06/Algorithm">https://github.com/yosupo06/Algorithm</a>)にもある程度ストレステストを書いたりしていたのだが,ライブラリの長さが制限されているコンテスト用にライブラリをカスタマイズする必要が出てきた(個人的にこのルールに思うところはあるが)。</p> <p>当然テストは流用できるのだが,どうせなら切り出して両方のライブラリから共通で使えるようにしてしまおうと思い,切り出している。</p> <h1>切り出したもの</h1> <p><a href="https://github.com/yosupo06/algorithm-test">https://github.com/yosupo06/algorithm-test</a></p> yosupo swap(v, v)は危ない?という話 hatenablog://entry/10257846132654163574 2018-10-16T03:42:31+09:00 2018-10-16T03:42:57+09:00 -D_GLIBCXX_DEBUGを付けて以下のコードを実行するとアサートにひっかかります(https://stackoverflow.com/questions/13129031/on-implementing-stdswap-in-terms-of-move-assignment-and-move-constructor)。 struct S { vector<int> a; } S s; swap(s, s); どうやらswap内部でs = std::move(s)が発生し、これがヤバイっぽい(UBを引き起す?)です。 競技プログラミングにおいては、ランダムな並び換え(これはshuffle… <p>-D_GLIBCXX_DEBUGを付けて以下のコードを実行するとアサートにひっかかります(<a href="https://stackoverflow.com/questions/13129031/on-implementing-stdswap-in-terms-of-move-assignment-and-move-constructor">https://stackoverflow.com/questions/13129031/on-implementing-stdswap-in-terms-of-move-assignment-and-move-constructor</a>)。</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synType">struct</span> S { vector&lt;<span class="synType">int</span>&gt; a; } S s; swap(s, s); </pre> <p>どうやらswap内部でs = std::move(s)が発生し、これがヤバイっぽい(UBを引き起す?)です。 <a class="keyword" href="http://d.hatena.ne.jp/keyword/%B6%A5%B5%BB%A5%D7%A5%ED%A5%B0%A5%E9%A5%DF%A5%F3%A5%B0">競技プログラミング</a>においては、ランダムな並び換え(これはshuffle使えばいいけど)や、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AC%A5%A6%A5%B9">ガウス</a>の掃き出し法などでswap(s, s)を使うことが多いと思います。</p> <h2>対策1</h2> <p>swap(s, s)をしないようにする。</p> <h2>対策2</h2> <p>UBと言ってもどうせ動くのでアサート自体を無効化する(<a href="https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle">https://stackoverflow.com/questions/22915325/avoiding-self-assignment-in-stdshuffle</a>)</p> <pre class="code lang-cpp" data-lang="cpp" data-unlink><span class="synPreProc">#include </span><span class="synConstant">&lt;debug/macros.h&gt;</span> <span class="synPreProc">#undef __glibcxx_check_self_move_assign</span> <span class="synPreProc">#define __glibcxx_check_self_move_assign(x)</span> <span class="synType">struct</span> S { vector&lt;<span class="synType">int</span>&gt; a; } S s; swap(s, s); </pre> yosupo