rep マクロ

#define rep(i, n) for (int i = 0; i < n; ++i)

例えば rep(i, k & 1) とすると for (int i = 0; (i < k) & 1; ++i) と等価であるため、意図しない動作を引き起こします: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

これを避けるには #define rep(i, n) for (int i = 0; i < (n); ++i) とすればよいです: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

#define rep(i, l, r) for (int i = (l); i < (r); ++i)

$l$ や $r$ は 32 bit 整数で表せないほど大きな整数かもしれません。

#define rep(i, l, r) for (long long i = (l); i < (r); ++i)

$l$ や $r$ は 64 bit 整数で表せないほど大きな整数かもしれません。

#define rep(i, l, r) for (__int128_t i = (l); i < (r); ++i)

速度的にあまり嬉しくなさそう。

#define rep(i, l, r) for (decltype(r) i = (l); i < (r); ++i)

r は参照型かもしれません: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

#define rep(i, l, r) for (std::remove_reference_t<decltype(r)> i = (l); i < (r); ++i)

rconst 修飾されているかもしれません: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

#define rep(i, l, r) for (std::decay_t<decltype(r)> i = (l); i < (r); ++i)

std::decay_t<decltype(r)>l を表現できない場合にバグります: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

こうすると signedness さえ一致していれば正しく動く: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

signedness が一致していない場合は unsigned の方が選ばれるので、rep(i, -1, 3U) などとするとバグる: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

signedness が一致しない場合にコンパイルエラーを吐かせることもできる: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

追記

上記実装で壊れるケースを頂きました:

r の計算が比較のたびに走って計算量が悪化することがあります。

#define rep(i, l, r) for (rep_int_t<std::decay_t<decltype(l)>, std::decay_t<decltype(r)>> i = (l), end_i = (r); i < end_i; ++i)

r を先に評価することで回避できる: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

ただし、end_i という変数が既に外側で使われていた場合に変数の shadowing が起こる: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

対処として考えられそうなのは次の 2 種類。

  1. 衝突しなさそうな変数名を使う
  2. rep 用の構造体を作る

例えば 2 であれば次のように実装できる: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

この場合、ループ中に i を変更したときの挙動が今までとは異なることに注意する: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

この挙動の違いは非自明なので、iconst で宣言してあげるのがよさそう: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

構造体にする利点

次のように同じ rep マクロで引数の個数によって挙動を使い分けたいとする。

  • rep(i, n) : $i = 0, 1, \ldots, n - 1$ の順に走査
  • rep(i, l, r) : $i = l, l +1, \ldots, r - 1$ の順に走査

c - Overloading Macro on Number of Arguments - Stack Overflow のような方法もあるが、構造体であればコンストラクタを増やして次のように簡単に書ける: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

一番初めに挙げた優先順位の問題は起こらないはず (多分...)

コードがかなり長くなってしまうのが嫌なら l, r の型の一致を強制することで短くできて、次のようになる: [C++] gcc HEAD 14.0.0 20230712 (experimental) - Wandbox

まとめ

この記事の結論としては次の 2 つをおすすめする。

AtCoder Grand Contest 058 D - Yet Another ABC String

問題

D - Yet Another ABC String

解法

包除原理により数える。

$0\leq i \leq A+B+C-3$ に対して条件 $P _ i$ を「$S _ iS _ {i + 1}S _ {i + 2}$ で条件に違反する」と定める。$X\subseteq \lbrace 0,1,\ldots, A+B+C-3\rbrace$ に対して、全ての $i\in X$ で $P _ i$ を満たすような $S$ の個数を $n(X)$ とする。このとき答えは以下で表される。

$$\sum _ {X\subseteq \lbrace 0,1,\ldots, A+B+C-3\rbrace} (-1) ^ {\lvert X\rvert} n(X).$$

$X$ の要素 $i, j$ が $\lvert j - i\rvert \lt 3$ を満たす場合、$S _ i$ と $S _ j$ を独立に決めることは出来ず、$S _ i$ の値から $S _ j$ の値が一意に定まる。

$S _ i$ の値から $S _ j$ の値が一意に定まるならば辺 $\lbrace i, j \rbrace$ を張ったグラフの連結成分は区間を成す。そこで、この区間を単位として遷移する DP を考える。

長さ $i + 3$ の区間による遷移の係数を考えるために $F$ を以下で定める。$F$ の $2$ つめの添字は $X$ の要素数の偶奇を表す。

$$\begin{aligned} F _ {i, 0} &= \begin{cases} 0 & \text{if } i \lt 0 \\ 0 & \text{if } i = 0 \\ F _ {i - 2, 1} + F _ {i - 1, 1} & \text{if } i \gt 0 \\ \end{cases}, \\ F _ {i, 1} &= \begin{cases} 0 & \text{if } i \lt 0 \\ 1 & \text{if } i = 0 \\ F _ {i - 2, 0} + F _ {i - 1, 0} & \text{if } i \gt 0 \\ \end{cases}. \end{aligned} $$

$F _ {i, 0} - F _ {i, 1}$ を計算してみると、次が成り立つ。

$$ F _ {i, 0} - F _ {i, 1} = \begin{cases} -1 & \text{if } i \equiv 0 \pmod{3} \\ 1 & \text{if } i \equiv 1 \pmod{3} \\ 0 & \text{if } i \equiv 2 \pmod{3} \\ \end{cases}. $$

従って、DP を形式的冪級数で解釈すると求める答えは以下で表される。

$$\begin{aligned} & \lbrack x ^ A y ^ B z ^ C\rbrack \sum _ {k = 0} ^ \infty \left((x + y + z) + \sum _ {i = 1} ^ \infty (xyz) ^ i (x+y+z) + \sum _ {i = 1} ^ \infty -3(xyz) ^ i \right) ^ k \\ = & \lbrack x ^ A y ^ B z ^ C\rbrack \sum _ {k = 0} ^ \infty \left(\dfrac{x + y + z - 3xyz}{1 - xyz} \right) ^ k \\ = & \lbrack x ^ A y ^ B z ^ C\rbrack \dfrac{1}{1 - \dfrac{x + y + z - 3xyz}{1 - xyz}} \\ = & \lbrack x ^ A y ^ B z ^ C\rbrack \dfrac{1 - xyz}{1 - ( (x + y + z) - 2xyz)} \\ = & \lbrack x ^ A y ^ B z ^ C\rbrack \dfrac{1}{1 - ( (x + y + z) - 2xyz)} - \lbrack x ^ {A - 1} y ^ {B - 1} z ^ {C - 1}\rbrack \dfrac{1}{1 - ( (x + y + z) - 2xyz)}. \\ \end{aligned}$$

あとは $\lbrack x ^ p y ^ q z ^ r\rbrack \dfrac{1}{1 - ( (x + y + z) - 2xyz)}$ の計算ができればよいが、これは以下のように計算できる。

$$\begin{aligned} & \lbrack x ^ p y ^ q z ^ r\rbrack \dfrac{1}{1 - ( (x + y + z) - 2xyz)} \\ = & \lbrack x ^ p y ^ q z ^ r\rbrack \sum _ {k = 0} ^ \infty ( (x + y + z) - 2xyz) ^ k \\ = & \sum _ {k = 0} ^ \infty \binom{k}{t _ k} (-2) ^ {k - t _ k} \dfrac{t _ k !}{(p - k + t _ k)! (q - k + t _ k)! (r - k + t _ k)!} \quad (t _ k \coloneqq (3k - (p + q + r) ) / 2) \\ = & \sum _ {k = 0} ^ {p + q + r} \binom{k}{t _ k} (-2) ^ {k - t _ k} \dfrac{t _ k !}{(p - k + t _ k)! (q - k + t _ k)! (r - k + t _ k)!}. \end{aligned}$$

全体の計算量は $\Theta(A + B + C)$ 時間である。

PowerPoint数式 (UnicodeMath) 備忘録

自分用。随時加筆予定。

ドキュメント

www.unicode.org

特に気を付けるべきこと

  1. 空白 (以降 で表す) の有無によってしばしば挙動が変わる

    左: n(n+1)/2␣ / 右: n␣(n+1)/2␣
    意味的にまとまりがある部分は空白を開けずに書くと概ね意図通りに組版されるような仕様になっている。

    ({} ではなく) () で明示的にまとまりを指定することもできて、(n(n+1))/2 の出力は左の画像と一致する。右図で $n+1$ に括弧が付いていないのは () がこの用途で認識されるため。どうしても $\dfrac{(n+1)}{2}$ と表示させたければ括弧を二重にして ((n+1))/2␣ と書く。

  2. latex{}() に置き換える

    \int_(0)^(\infty␣)␣e^(-x^2␣)␣"d"␣x
    1 でも言及した通り、明示的なまとまりの指定には {} ではなく () を用いる。

  3. 複数行数式 (\cases, \matrix, \eqarray, ...) では改行に @ を使う

    (\matrix(1&0@0&1)␣)␣

数式モードへの入り方

  • Windows
    • US配列: Alt+=
    • JIS配列: Alt+;
  • 他: 知らない

フォント

  • mathrm ($\mathrm{ABC}\cdots$): " で囲う
  • mathcal ($\mathcal{ABC}\cdots$): \scriptA, \scriptB, ...
  • mathfrak ($\mathfrak{ABC}\cdots$): \fracturA, \fracturB, ...
  • mathbb ($\mathbb{ABC}\cdots$): \doubleA, \doubleB, ...
  • mathsf ($\mathsf{ABC}\cdots$): 不明
  • mathscr ($\mathscr{ABC}\cdots$): 不明

記号

「$\lor,\land$ を書きたいが \lor, \landが使えないので代わりに \vee\wedge を使わないといけない」というようなことがしばしば起こる。こういうときは、記号一覧表が公式ドキュメントの Appendix にあるのでこれを見るのが早い。

数学関数

$\sin,\exp,\log$ などは \ を付けずに sin␣x, exp␣x, log␣x, log_2␣x などとすればよい。平方根 $\sqrt x$ は \sqrt␣x でよいが、冪乗根 $\sqrt[n]{x}$ の記法には注意が必要で、\sqrt(n&x)␣ あるいは \root␣n\of␣x␣と書く必要がある。

\underbrace

\underbrace␣(x+\cdots+x)_(k)␣, \overbrace␣(x+\cdots+x)^(k)␣, \underparen␣(x+\cdots+x)_(k)␣, \overparen␣(x+\cdots+x)^(k)␣ でそれぞれ以下のようになる。

複数行にわたる数式

揃えたい位置に & を打つのは latex と同じだが、改行は @ で行うので注意。

  • \cases

    |x|=\cases(-&x("if␣"␣x<0)@&x("otherwise"␣))␣

  • \eqarray

    \eqarray(x_1␣&+&2x_2␣&+&3x_3␣&=&1@-x_1␣&-&x_2␣&-&x_3␣&=&2)␣
    左に { を付けたい場合はこう↓

    {\eqarray(x_1␣&+&2x_2␣&+&3x_3␣&=&1@-x_1␣&-&x_2␣&-&x_3␣&=&2)␣\close␣␣

  • \matrix

    \matrix(...)␣ (... の部分の書き方は \eqarray とかと同じ) とすれば行列が書ける。後述の括弧の大きさの自動調整を使って (\matrix(...)␣)␣, [\matrix(...)␣]␣, |\matrix( ... )␣|␣ とすればそれぞれ latex\pmatrix, \bmatrix, \vmatrix と同様のことができる。

括弧の大きさの調整

  • 自動調整 (\left, \right に相当するもの)

    (), {}, [], ⟨⟩, ||, ⌊⌋, ⌈⌉ などに関しては閉じ括弧の後に空白を打てば自動で大きさを調整してくれる。半開区間 [)Dirac notation ⟨|, |⟩ なども閉じ括弧の後に空白を打てば同じように上手くやってくれる。

    だめな場合もあって、例えば $\left] \dfrac{b}{a}, \dfrac{d}{c}\right[$ なんかは ]b/a␣,d/c␣[␣ としても上手く行かない。この場合は (\left, \right にそれぞれ対応する) \open, \close を用いて \open␣]b/a,d/c␣\close␣[␣ とすればよい。

    また下画像のように集合の内包表記をする場合などは | も左右の括弧 {} に合わせてサイズを大きくしたくなる。

    集合の内包表記

    これは素直に {x\in␣S\mid␣\eqarray(&y<x-1\vee␣&y>x+1)␣}␣ と書けば実現できる。ただし条件部分に記号 | が使われている (例えば "$d$ は $x$ を割り切る" などを d|xd\mid␣x と書いて条件部分に入れるなどする) と調整が思い通りにならないことがある。対処としては後から手動で修正するしか無さそう?

  • 手動調整 (\big などに相当するもの)

    括弧の大きさを自分で指定したい場合、例えば \open␣0(b/a␣,d/c␣\close␣0)␣ とすると括弧は強制的にデフォルトの大きさとなる。0 の部分を 1, 2, ..., 4 に変えるとそれぞれ latex でいう \big, \Big, \bigg, \Bigg に対応する大きさになる。

AtCoder Regular Contest 120 F - Wine Thief

問題

F - Wine Thief

解法

数列は 0-indexed とする。

$\displaystyle \mathcal{B} _ {i, p} = \left\lbrace (B _ 0, \ldots, B _ {i - 1}) \in \lbrace 0, 1\rbrace ^ i \mid B _ j + B _ {j + 1} \neq 2 \ (\forall j\in\lbrace 0,1,\ldots, i-2\rbrace) \land B _ {i - 1} = p \right\rbrace$ とする。これは、ラフに言えば、前 $i$ 個のワインに対する盗む(=1)/盗まない(=0)の valid な決め方であって、ワイン $i - 1$ を盗むかどうかが $p$ であるようなもの全体の集合である。

以下のような、多項式を要素に持つ動的計画法を考える。ただし組 $(i, p, t)$ は $i \in \lbrace 0, 1, \ldots, n\rbrace, p\in\lbrace 0, 1\rbrace, t\in\lbrace 0, 1\rbrace$ の範囲を動く。

$$ f _ {i, p, t}(x) \coloneqq \sum _ {B \in \mathcal{B} _ {i, p}} \sum _ {j = 0} ^ {i - 1} B _ j A _ j ^ t x ^ {\sum _ {j = 0} ^ {i - 1} B _ j}. $$

$x$ の指数 $\sum _ {j = 0} ^ {i - 1} B _ j$ は盗むワインの個数に対応する。$p\in \lbrace 0, 1\rbrace$ を状態に持つのは次のワインを盗めるかの判定のためである。$t\in\lbrace 0, 1\rbrace$ は $A$ の $0$ 乗和と $1$ 乗和をそれぞれ持つことを意味する。

求める答えは $\lbrack x ^ K \rbrack (f _ {n, 0, 1} + f _ {n, 1, 1})$ である。遷移は次のように行列で表現できる。

$$\left(\begin{array}{cc:cc} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \\ \hdashline x & 0 & 0 & 0 \\ A _ ix & x & 0 & 0 \\ \end{array}\right)\begin{pmatrix} f _ {i, 0, 0} \\ f _ {i, 0, 1} \\ \hdashline f _ {i, 1, 0} \\ f _ {i, 1, 1} \end{pmatrix} = \begin{pmatrix} f _ {i+1, 0, 0} \\ f _ {i+1, 0, 1} \\ \hdashline f _ {i+1, 1, 0} \\ f _ {i+1, 1, 1} \end{pmatrix}.$$

上 $2$ 行が $i$ 番目のワインを盗まない場合の遷移、下 $2$ 行が $i$ 番目のワインを盗む場合の遷移に対応する。左 $2$ 列は $i - 1$ 番目のワインを盗まない場合からの遷移、右 $2$ 列は $i - 1$ 番目のワインを盗む場合からの遷移を表す。

従って $T _ i \coloneqq \begin{pmatrix} 1 & 0 & 1 & 0 \\ 0 & 1 & 0 & 1 \\ x & 0 & 0 & 0 \\ A _ ix & x & 0 & 0 \\ \end{pmatrix}$ と定めれば次が成り立つ。

$$T _ {n - 1} T _ {n - 2} \cdots T _ 0\begin{pmatrix}1 \\ 0 \\ 0 \\ 0\end{pmatrix} = \begin{pmatrix} f _ {n, 0, 0} \\ f _ {n, 0, 1} \\ f _ {n, 1, 0} \\ f _ {n, 1, 1} \end{pmatrix}.$$

$T _ 0, T _ 1, \ldots, T _ {n - 1}$ を順にベクトルに掛けると $\Theta(n ^ 2)$ 時間掛かるが、二分木状に積を取る分割統治法を用いて $T _ {n - 1} T _ {n - 2} \cdots T _ 0$ を計算してからベクトルに掛けることで (定数倍の非常に重い) $\Theta(n (\log n) ^ 2)$ 時間で本問題を解くことができる。

なお、本解法では全ての $K = 0, 1, \ldots, \lceil N/2\rceil$ に対する答えを計算できる。

実装

https://atcoder.jp/contests/arc120/submissions/42033491

実行時間制限に間に合わせるため、いくつか定数倍高速化を行っている。

  • $T _ i$ の積として得られる行列は全て $\begin{pmatrix} \ast & 0 & \ast & 0 \\ \ast & \ast & \ast & \ast \\ \ast & 0 & \ast & 0 \\ \ast & \ast & \ast & \ast \\ \end{pmatrix}$ という形を持つため、$0$ であることが予め分かっている要素の計算を省いてよい
  • 分割統治の過程において次数が $2$ 冪の多項式が多く現れる。$2 ^ k$ 次の多項式うしの積は $2 ^ {k + 1}$ 次であり、配列のサイズとしては $2 ^ {k + 1} + 1$ となるため FFT の定数倍が悪化する。そこで一方の $2 ^ k$ 次の項を削って畳み込んだ後に削った項の寄与を足しこむことで、定数倍を削減できる。

第12回 アルゴリズム実技検定 感想

65:09 で完走しました。

解説は公式解説があるので、感想を書きます。

C - 偏ったサイコロ

$$\mathsf{dp}(i, j) \coloneqq 1 \text{ 個目から } i \text{ 個目までの出目の和がちょうど } j \text{ である確率}$$

として dp を書いたけど、想定解は出目の全探索だったらしい。

D - 採点

$u \leq v$ となるように swap してから set に入れるのはやや面倒なので、std::minmax(u, v) を insert すると楽。

E - 棒倒しゲーム

題意の理解に時間が掛かった。$s$ を $R$ 個の連続部分列に分ける方法であって、各連続部分列が $1$ ラウンドに対応するようにできるかという問題。

左端を固定すると、右端の候補は

  1. 連続部分列の和が $N$ 以上になる最初の位置
  2. $\text{左端}+ M - 1$
  3. $L$

のうち最小を調べれば十分。この方針に従って前から連続部分列を取り除いていき、以下を調べれば十分。

  1. 取り除いた連続部分列の個数がちょうど $R$ か
  2. 取り除いた連続部分列たちが全て $1$ ラウンドの要件を満たすか

サンプルは 1 つめの条件を調べなくても合うので忘れないよう注意 (これで WA を出しかけた)。

F - 薬剤師

目に入る制約がいかつくて構えたけど、よく見るとクエリ毎に全探索しても十分間に合うようになっている。

ただ、「$y$ が $X _ i$ に含まれるか?」という問いには十分高速に答えられるよう前処理しておく必要がある。

H - 3種の硬貨

これは個人的な癖かもしれないけど最初に考えたのは

  1. 貰う金貨の最大化
  2. 払う銀貨の最小化
  3. 払う銅貨の最小化

で、これだと最大と最小が入り混じって比較が面倒。最大化に統一して次のようにすると、operator< で比較できる (std::tuple<int, int, int> で表現した場合)。

  1. 貰う金貨の最大化
  2. 残りの銀貨の最大化
  3. 残りの銅貨の最大化

この方針に従って dp テーブルを定義すると公式解説と同じものになる。

I - 毎日のリンゴ

迷わず ACL の floor_sum を貼ったけど、想定解 1 を見て確かにになった...

J - スプリンクラー

手計算した式を書くだけなんだけど、計算に時間が掛かってしまった。

K - 連結チェック

Dynamic Connectivity を借りて貼ろうかとも思ったけど、自重。

L - 展覧会

最終的な状態を全探索するテクを久しぶりに見た気がする。

M - シリーズ

C - 蛍光灯 という全く同じ問題がある。初見で感動して記憶に残っていたのですぐ反応できた。

N - 上からと横から

$2$ 数の積の上限が決まっていると小さい方は上限の sqrt 以下になるという典型。AOJ の問題で初めてこの典型を知った気がするけど、どれだったか思い出せない。

O - 2個のボール

多次元畳み込み。

$$\begin{aligned} f(x, y) &= \sum _ {i = 0} ^ {2 ^ N - 1} A _ i x ^ {i} y ^ {\mathsf{popcount}(i)}, \\ g(x, y) &= \sum _ {i = 0} ^ {2 ^ N - 1} B _ i x ^ {i} y ^ {\mathsf{popcount}(i)}. \end{aligned}$$

とする。$x$ に関する積は $x ^ i \bullet x ^ j = x ^ {i \mathop{|} j}$ で、$y$ に関する積は $\ y ^ i \circ y ^ j = y ^ {i + j}$ として $f$ と $g$ の積を計算すればよい。

この積は subset zeta 変換 (x 方向) -> DFT (y 方向) -> 各点積 -> inv DFT (y 方向) -> subset mobius 変換 (x 方向) で計算できる。(or convolution 自体も $\mathrm{mod}\ (x _ 1 ^ 2 - x _ 1, x _ 2 ^ 2 - x _ 2, \ldots, x _ N ^ 2 - x _ N)$ の多次元畳み込みではある)

関連: Ex - No-capture Lance Game

集合冪級数 (Set Power Series) のダブリングまとめ

$\gdef\all#1{\lbrack #1\rbrack}$ $\gdef\powall#1{2 ^ {\all{#1}}}$ $\gdef\set#1{\lbrace#1\rbrace}$ $\gdef\F{\mathcal{F}}$

追記

本記事の内容に関してより統一的な見方をしていそうな記事を教えて頂きました。

https://codeforces.com/blog/entry/92183

追記 (2023/05/02): 上記事 (多項式との合成) に関する内容を追加しました。

導入

非負整数 $n$ に対して $\all{n} \coloneqq \set{0,1,\ldots,n-1}$ と定義します。

集合関数 $f\colon \powall{n} \to \mathbb{K},\ g\colon \powall{n} \to \mathbb{K}$ の subset convolution $f\ast g$ を以下で定義します。

$$f\ast g\colon S\in \powall{n} \mapsto \sum _ {X\subseteq S} f(X) g(S\setminus X) \in\mathbb{K}.$$

与えられた $f,g$ に対して $f\ast g$ を $O(2 ^ n n ^ 2)$ 時間で計算するアルゴリズムが知られています*1

$R _ n$ を $\powall{n}$ から $\mathbb{K}$ への関数全体の集合として $(R _ n, +, \ast)$ は可換環を成します。ここで、加法 $+$ は通常の加法で $f+g\colon S\in \powall{n} \mapsto f(S) + g(S)\in\mathbb{K}$ です。

加法単位元 $e _ 0$ および乗法単位元 $e _ 1$ はそれぞれ次で表されます。

$$\begin{aligned} e _ 0 &\colon S\in \powall{n} \mapsto 0 \in\mathbb{K}, \\ e _ 1 &\colon S \in \powall{n} \mapsto \begin{cases} 1 & \text{if}\quad S =\emptyset \\ 0 & \text{otherwise} \end{cases}. \end{aligned}$$

本記事では、この環上での様々な操作をダブリングによって計算する方法を与えます。

ranked zeta transform -> 各点に対する演算 -> ranked mobius transform とする計算方法 *2 もありますが、ダブリングによる実装は subset convolution さえ書いてしまえば fps exp / log / pow などを書かずに済むという利点があります。ただ、後述しますが一部のケースはダブリングで対応することが (おそらく) 難しいことに注意してください。

ダブリングの基本方針

計算結果 $h\in R _ n$ を得るアルゴリズムを以下の手順で得ます。

  1. $h(\emptyset)$ を得る
  2. $m\in\all{n}$ を固定し、$S\subseteq \all{m}$ なる全ての $S$ に対して $h(S)$ が求まっていると仮定して $m\in T\subseteq \all{m + 1}$ なる全ての $T$ に対する $h(T)$ を計算する方法を得る

これをダブリングと呼んでいるのは、$m=0,1,\ldots,n-1$ の順に 2 を行うことで既知の $h(\bullet)$ の個数が倍々に増えていくことが理由です。

記法

  • 集合 $S, T$ に対して $S \sqcup T$ で $S\cap T = \emptyset$ であるような $S, T$ に対する $S\cup T$ を表します (disjoint であることを明示的に示すための記法)。
  • 表記の簡単のため、$x\in\all{n}$ に対して $S + x$ で $S \sqcup \set{x}$ を表します。
  • $m\in\all{n}$ と $f\in R _ n$ に対して $\mathsf{lo} _ m (f)$ および $\mathsf{hi} _ m (f)$ を以下で定義します。 $$\begin{aligned} \mathsf{lo} _ m (f) &\colon S\in\powall{m}\mapsto f(S) \in \mathbb{K}, \\ \mathsf{hi} _ m (f) &\colon S\in\powall{m}\mapsto f(S+m) \in \mathbb{K}. \end{aligned}$$

inv

$f\in R _ n$ に対して $f ^ {-1}$ を $f\ast g = e _ 1$ を満たす $g\in R _ n$ と定義します。ただし $f(\emptyset) \neq 0$ を仮定します。

  1. $f ^ {-1}(\emptyset)$
    $f(\emptyset) f ^ {-1}(\emptyset) = e _ 1(\emptyset) = 1$ より $f ^ {-1}(\emptyset) = (f(\emptyset) ) ^ {-1}$ です。
  2. $f ^ {-1}(S+m)$ for all $S\subseteq \all{m}$
    $S\subseteq \all{m}$ に対して $e _ 1(S + m) = 0$ より、次が成り立ちます。 $$\begin{aligned} 0 &= \sum _ {X\subseteq S + m} f( (S + m) \setminus X) f ^ {-1}(X) \\ &= \sum _ {X\subseteq S} f( (S \setminus X) + m) f ^ {-1}(X) + \sum _ {X\subseteq S} f(S \setminus X) f ^ {-1}(X + m) \\ &= (\mathsf{hi} _ m(f) \ast \mathsf{lo} _ m(f ^ {-1}) + \mathsf{lo} _ m(f) \ast \mathsf{hi} _ m(f ^ {-1}) )(S). \end{aligned}$$ いま求めたいのは $\mathsf{hi} _ m(f ^ {-1})$ です。$\mathsf{lo} _ m(f) ^ {-1} = \mathsf{lo} _ m(f ^ {-1})$ なので、次が成り立ちます。 $$\mathsf{hi} _ m(f ^ {-1}) = - \mathsf{hi} _ m(f) \ast \mathsf{lo} _ m(f ^ {-1}) \ast \mathsf{lo} _ m(f ^ {-1}).$$

計算量を $T(n)$ とすると、$T(n) = T(n - 1) + O(2 ^ n n ^ 2)$ より $T(n) = O(2 ^ n n ^ 2)$ です。

exp

$\displaystyle \exp f \coloneqq \sum _ {k = 0} ^ \infty \dfrac{f ^ k}{k!}$ と定義されます。ただし $f(\emptyset) = 0$ を仮定します。($f(\emptyset) \neq 0$ であっても $\exp (f(\emptyset) )$ が $\mathbb{K}$ において定義されるならば正当そうなんですが、一旦正当性が簡単でかつ実用上でもよく用いられるであろう $f(\emptyset) = 0$ のケースのみを考えます)

  1. $(\exp f)(\emptyset)$
    $\displaystyle (\exp f)(\emptyset) = \sum _ {k = 0} ^ \infty \dfrac{f(\emptyset) ^ k}{k!} = e ^ 0 = 1$ です。
  2. $(\exp f)(S+m)$ for all $S\subseteq \all{m}$
    $S\subseteq \all{m}$ に対して、次が成り立ちます。 $$\begin{aligned} (\exp f)(S + m) &= \sum _ {k = 0} ^ {\infty} \dfrac{1}{k!} \sum _ {X _ 1 \sqcup X _ 2\sqcup \cdots\sqcup X _ k = S + m} \prod _ {i = 1} ^ k f(X _ i)\\ &= \sum _ {k = 1} ^ {\infty} \dfrac{k}{k!} \sum _ {Y \subseteq S} \sum _ {X _ 1 \sqcup X _ 2\sqcup \cdots\sqcup X _ {k - 1} = S \setminus Y} f(Y + m) \prod _ {i = 1} ^ {k - 1} f(X _ i)\\ &= \sum _ {Y \subseteq S} f(Y + m) \sum _ {k = 0} ^ {\infty} \dfrac{1}{k!} \sum _ {X _ 1 \sqcup X _ 2\sqcup \cdots\sqcup X _ {k} = S \setminus Y} \prod _ {i = 1} ^ {k} f(X _ i)\\ &= \sum _ {Y \subseteq S} (\mathsf{hi} _ m (f)) (Y) \cdot (\exp f)(S \setminus Y)\\ &= (\mathsf{hi} _ m (f)\ast \mathsf{lo} _ m(\exp f))(S). \end{aligned}$$ 途中で無限和の順番を交換しましたが、$f(\emptyset) = 0$ より $k \gt n$ において $\displaystyle \dfrac{f ^ k}{k!} = e _ 0 \equiv 0\ (\text{const.})$ なので正当です。
    いま求めたいのは $\mathsf{hi} _ m(\exp f)$ ですが、これは上式より $\mathsf{hi} _ m (f)\ast \mathsf{lo} _ m(\exp f)$ として計算できます。

計算量は inv のときと同様の解析により $O(2 ^ n n ^ 2)$ 時間です。

log

$\log$ は $\exp$ の逆関数なので、次が成り立ちます。ただし $f(\emptyset) = 1$ を仮定します。

$$\begin{aligned} (\log f)(\emptyset) &= \log (f(\emptyset)) = 0, \\ \mathsf{hi} _ m(\log f) &= \mathsf{hi} _ m(f) \ast \mathsf{lo} _ m(f) ^ {-1} = \mathsf{hi} _ m(f) \ast \mathsf{lo} _ m(f ^ {-1}). \end{aligned}$$

計算量は $O(2 ^ n n ^ 2)$ 時間です。

pow

$k$ を非負整数として $f ^ k$ の計算を考えます。$k = 0$ のときは $f ^ k = e _ 1$ なので、以下では $k \gt 0$ を仮定します。

今までとは少し異なる方針で計算します。$S\subseteq\all{n - 1}$ に対して、次が成り立ちます。

$$\begin{aligned} f ^ k(S + (n - 1)) &= \sum _ {X _ 1\sqcup X _ 2\sqcup \cdots \sqcup X _ k = S + (n - 1)} \prod _ {i = 1} ^ k f(X _ i) \\ &= k\sum _ {Y\subseteq S} f(Y + (n - 1)) \sum _ {X _ 1\sqcup X _ 2\sqcup \cdots \sqcup X _ {k - 1} = S \setminus Y} \prod _ {i = 1} ^ {k - 1} f(X _ i) \\ &= k\times (\mathsf{hi} _ {n - 1}(f) \ast \mathsf{lo} _ {n - 1}(f) ^ {k - 1}) (S). \end{aligned}$$

つまり、$\mathsf{hi} _ {n - 1} (f ^ k) = k\times(\mathsf{hi} _ {n - 1}(f) \ast \mathsf{lo} _ {n - 1}(f) ^ {k - 1})$ です。一方で次が成り立ちます。

$$\mathsf{lo} _ {n - 1}(f ^ k) = \mathsf{lo} _ {n - 1}(f) ^ k = \mathsf{lo} _ {n - 1}(f) \ast \mathsf{lo} _ {n - 1}(f) ^ {k - 1}.$$

$\mathsf{lo} _ {n - 1}(f ^ k)$ および $\mathsf{hi} _ {n - 1}(f ^ k)$ に共通して $\mathsf{lo} _ {n - 1}(f) ^ {k - 1}$ が現れていることに注意します。再帰的に $\mathsf{lo} _ {n - 1}(f) ^ {k - 1}$ を計算することにすれば計算量 $T(n)$ は $T(n) = T(n - 1) + O(2 ^ n n ^ 2)$ より $T(n) = O(2 ^ n n ^ 2)$ です。

sqrt

$f\in R _ n$ に対して $f ^ {1/2}$ を $g ^ 2 = f$ を満たす $g\in R _ n$ と定義します (一般に $g$ は複数存在し得ます)。ただし $f(\emptyset) \neq 0$ かつ $(f(\emptyset) ) ^ {1/2}$ の存在を仮定します。

  1. $f ^ {1/2}(\emptyset)$
    $f ^ {1/2}(\emptyset) f ^ {1/2}(\emptyset) = f(\emptyset)$ より $f ^ {1/2}(\emptyset) = (f(\emptyset) ) ^ {1/2}$ です。
  2. $f ^ {1/2}(S+m)$ for all $S\subseteq \all{m}$
    $S\subseteq \all{m}$ に対して、次が成り立ちます。 $$\begin{aligned} f(S + m) &= \sum _ {X\subseteq S + m} f ^ {1/2}( (S + m) \setminus X) f ^ {1/2}(X) \\ &= \sum _ {X\subseteq S} f ^ {1/2}( (S \setminus X) + m) f ^ {1/2}(X) + \sum _ {X\subseteq S} f ^ {1/2}(S \setminus X) f ^ {1/2}(X + m) \\ &= 2 \times (\mathsf{hi} _ m(f ^ {1/2}) \ast \mathsf{lo} _ m(f ^ {1/2}) )(S). \end{aligned}$$ いま求めたいのは $\mathsf{hi} _ m(f ^ {1/2})$ です。次が成り立ちます。 $$\mathsf{hi} _ m(f ^ {1/2}) = \dfrac{1}{2}(\mathsf{hi} _ m(f) \ast \mathsf{lo} _ m(f ^ {1/2}) ^ {-1} ).$$

以上より $O(2 ^ n n ^ 2)$ 時間で計算できます。

これを一般化して $1/k$ 乗を計算することもできます。詳細な計算式は省略しますが、次が成り立ちます。

  • $f ^ {1/k} (\emptyset) = (f(\emptyset) ) ^ {1/k}$
  • $\mathsf{hi} _ m(f ^ {1/k}) = \dfrac{1}{k}(\mathsf{hi} _ m(f) \ast (\mathsf{lo} _ m(f ^ {1/k}) ^ {-1}) ^ {k - 1} ) = \dfrac{1}{k}(\mathsf{hi} _ m(f) \ast \mathsf{lo} _ m(f ^ {1/k}) \ast \mathsf{lo} _ m (f ^ {-1}) )$

つまり、$1/k$ 乗も $O(2 ^ n n ^ 2)$ 時間で計算できます。有理数乗も $f ^ {p / q} = (f ^ {1/q}) ^ p$ により $O(2 ^ n n ^ 2)$ 時間です。

$f(\emptyset) = 0$ の場合に関して、私は ranked zeta transform -> 各点 sqrt -> ranked mobius transform とする計算方法しか知りません。ダブリングでも解けたら教えていただけると嬉しいです。

追記(2023/05/02): 多項式との合成

https://codeforces.com/blog/entry/92183 の内容です。

多項式 $\displaystyle a(x) = \sum _ {i = 0} ^ {m - 1} a _ i x ^ i$ と集合冪級数 $f$ の合成として得られる集合冪級数 $\displaystyle g \coloneqq a\circ f = \sum _ {i = 0} ^ {m - 1} a _ i f ^ i$ を計算します。

pow と同様の方針で再帰式を立てます。$S\subseteq\all{n - 1}$ に対して、次が成り立ちます。

$$\begin{aligned} g(S + (n - 1)) &= \sum _ {i = 0} ^ {m - 1} a _ i \sum _ {X _ 1\sqcup X _ 2\sqcup \cdots \sqcup X _ i = S + (n - 1)} \prod _ {j = 1} ^ i f(X _ j) \\ &= \sum _ {i = 1} ^ {m - 1} ia _ i \sum _ {Y\subseteq S} f(Y + (n - 1)) \sum _ {X _ 1\sqcup X _ 2\sqcup \cdots \sqcup X _ {i - 1} = S \setminus Y} \prod _ {j = 1} ^ {i - 1} f(X _ j) \\ &= \sum _ {Y\subseteq S} f(Y + (n - 1)) \sum _ {i = 1} ^ {m - 1} ia _ i \cdot f ^ {i - 1} (S \setminus Y) \\ &= \sum _ {Y\subseteq S} f(Y + (n - 1)) \cdot (a' \circ f)(S\setminus Y) \\ &= (\mathsf{hi} _ {n - 1}(f) \ast \mathsf{lo} _ {n - 1}(a' \circ f))(S) \\ &= (\mathsf{hi} _ {n - 1}(f) \ast (a' \circ\mathsf{lo} _ {n - 1}(f) ) )(S). \end{aligned}$$

つまり、$\mathsf{hi} _ {n - 1}(a\circ f) = \mathsf{hi} _ {n - 1}(f) \ast (a' \circ \mathsf{lo} _ {n - 1}(f))$ が成り立ちます。また $\mathsf{lo} _ {n - 1}(a\circ f) = a\circ \mathsf{lo} _ {n - 1}(f)$ です。

再帰式に微分 $a'$ が現れています。再帰を展開していくと、様々な整数組 $(i, j)$ に対して合成 $\displaystyle \left(\dfrac{\mathrm{d} ^ j a}{\mathrm{d} x ^ j}\right) \circ \mathsf{lo} _ {i}(f)$ が計算できれば十分なことが分かります。

そこで、次のように dp テーブルを定義します。求める値は $\mathsf{dp}(n, 0)$ です。

$$\mathsf{dp}(i, j) \coloneqq \left(\dfrac{\mathrm{d} ^ j a}{\mathrm{d} x ^ j}\right) \circ \mathsf{lo} _ {i}(f).$$

先ほどの再帰式より、このテーブルは次のように $i$ の昇順に埋めることができます。

$$\begin{aligned} \mathsf{dp}(0, j) &= \left(S \in \lbrace \emptyset \rbrace \mapsto \dfrac{\mathrm{d} ^ j a}{\mathrm{d} x ^ j}(f(S)) \in \mathbb{K}\right), \\ \mathsf{lo} _ {i - 1}(\mathsf{dp}(i, j)) &= \mathsf{dp}(i - 1, j), & \quad (1\leq i\leq n)\\ \mathsf{hi} _ {i - 1}(\mathsf{dp}(i, j)) &= \mathsf{hi} _ {i - 1}(f) \ast \mathsf{dp}(i - 1, j + 1). & \quad (1\leq i\leq n)\\ \end{aligned}$$

また、見るべき $i, j$ は $0\leq i \leq n,\ 0\leq j \leq n - i$ を満たす部分で十分なことが分かります。

計算量を評価します。$\mathsf{dp}(0,\ast)$ の計算は $O(nm)$ 時間です。$i = 1, 2, \ldots, n$ に対して、$\mathsf{dp}(i,\ast)$ の計算は大きさ $2 ^ {i - 1}$ の subset convolution を $n - (i - 1)$ 回行う部分がボトルネックとなり $O(2 ^ i i ^ 2 (n - i))$ 時間です。

以上より、テーブル全体を埋めるのに掛かる時間計算量は $O(nm + 2 ^ n n ^ 2)$ です ($\displaystyle \sum _ {i = 1} ^ n 2 ^ i i ^ 2 (n - i) = O(2 ^ n n ^ 2)$ に注意)。

なお、テーブルのデータとして ranked zeta transform を行った状態のものを持っておくことで、ボトルネックとなる subset convolution の定数倍を改善できます ( 実装例 )。

おわりに

ここで示した式通りに計算するプログラムを書いて簡単な動作確認はしているので正しいと思っていますが、万が一間違いがあれば教えてください。

AtCoder ユーザー解説まとめ

AtCoder Beginner Contest 219 G - Propagation

問題 https://atcoder.jp/contests/abc219/tasks/abc219_g
解説 https://atcoder.jp/contests/abc219/editorial/2670

コメント (折り畳み) クエリ平方分割による解法の説明です。

AtCoder Beginner Contest 238 F - Two Exams

問題 https://atcoder.jp/contests/abc238/tasks/abc238_f
解説 https://atcoder.jp/contests/abc238/editorial/3387

コメント (折り畳み)

平衡二分木を用いることで計算量を $O(N ^ 2 \log N)$ 時間に改善する方法について書いています。

AtCoder Beginner Contest 162 E - Sum of gcd of Tuples (Hard)

問題 https://atcoder.jp/contests/abc162/tasks/abc162_e
解説 https://atcoder.jp/contests/abc162/editorial/3469

コメント (折り畳み) $O(K ^ {\frac{2}{3}} (\log \log K) ^ {\frac{1}{3}} + K ^ {\frac{1}{2}} \log N)$ 時間解法の解説です。

  • $N$ を固定したとき、$\lfloor N/x \rfloor$ の値で整数 $x$ を分類すると、高々 $2\lfloor\sqrt{N}\rfloor$ 個の区間に分けられること
  • トーシェント関数 $\varphi$ の prefix sum の高速計算 (https://yukcoder.me/wiki/sum_totient)

を用いて高速化します。

AtCoder Beginner Contest 246 F - typewriter

問題 https://atcoder.jp/contests/abc246/tasks/abc246_f
解説 https://atcoder.jp/contests/abc246/editorial/3727

コメント (折り畳み) 非想定解法です。

AtCoder Beginner Contest 256 F - Cumulative Cumulative Cumulative Sum

問題 https://atcoder.jp/contests/abc256/tasks/abc256_f
解説 https://atcoder.jp/contests/abc256/editorial/4152

コメント (折り畳み) 数列 $A$ に対して累積和を 3 回取ってできる数列 $D$ の $k$ 項目を表す式を母関数を用いて機械的に導出する方法を説明しています。

dwangoプログラミングコンテスト D - タクシー

問題 https://atcoder.jp/contests/dwango2015-prelims/tasks/dwango2015_prelims_4
解説 https://atcoder.jp/contests/dwango2015-prelims/editorial/4570

コメント (折り畳み) 公式解説で説明が省略されている部分の補足と $\displaystyle \min _ x \sum _ {i = 1} ^ N w _ i \lvert x - t _ i \rvert$ の計算に関する別解について説明しています。

AtCoder Beginner Contest 278 G - Generalized Subtraction Game

問題 https://atcoder.jp/contests/abc278/tasks/abc278_g
解説 https://atcoder.jp/contests/abc278/editorial/5253

コメント (折り畳み) $O(N ^ 2)$ 時間で Grundy 数を全て計算する解法です。

AtCoder Beginner Contest 213 H - Stroll

問題 https://atcoder.jp/contests/abc213/tasks/abc213_h
解説 https://atcoder.jp/contests/abc213/editorial/5488

コメント (折り畳み) 形式的冪級数を要素とする行列を考えることで導出される $O(N ^ 3 T \log T)$ 時間解法です。

AtCoder Beginner Contest 289 F - Teleporter Takahashi

問題 https://atcoder.jp/contests/abc289/tasks/abc289_f
解説 https://atcoder.jp/contests/abc289/editorial/5739

コメント (折り畳み) $i$ step 後に存在しうる点の集合はおおよそ矩形領域として記述できることを用いて implicit に BFS を行う解法です。BFS の性質から最短遷移を計算できます。

AtCoder Beginner Contest 289 Ex - Trio

問題 https://atcoder.jp/contests/abc289/tasks/abc289_h
解説 https://atcoder.jp/contests/abc289/editorial/5749

コメント (折り畳み) 公式解説で Bonus として言及されている人が $n\leq 10 ^ 5$ 人いる場合の問題を $O(n (\log n) ^ 2 + T (\log T) ^ 2)$ 時間で解く解法です。

人 $1$ から人 $i$ への相対座標 $n-1$ 個を不定元として持つ多次元の形式的冪級数を考えると連続する $j$ に対する $\prod _ {i = 1} ^ n (j + a _ i)!$ や $\prod _ {i = 1} ^ n (j - a _ i)!$ の形の計算へと落ちます。差分更新を考えると、差分の計算は $\prod _ {i = 1} ^ n (x + a _ i)$ や $\prod _ {i = 1} ^ n (x - a _ i)$ の多点評価へと帰着されます。