累積和を抽象化し、一般の累積演算を考える(累積GCD、累積XOR、他)

注意事項
対象の読者として、「累積和について一応わかっていて、単純な累積和の問題は解ける」くらいを想定しています。
「累積和って何?初めて聞いたよ!」という人は読んでもキツいと思います。
また、やや抽象的な議論になります。


競技プログラミングには、累積和の他にも、累積××というような解法が存在する。
AtCoderの以下の問題では「累積GCD」という解法が登場した(GCD=最大公約数)。
C - GCD on Blackboard

あとは累積XORを使う問題も存在する。

累積和・累積GCD・累積XORという手法があるが、それぞれをバラバラに考えていると良くない。 仮に、他の演算に対して累積演算を考えて解かなければいけない問題があったとすると、 解法に全く気づかずに「しまったー!これは累積ナントカだったのか!」というハメになる。
そこで、個々の演算を離れて抽象化をし、どういう演算について「累積××」ができるのだろうか、を考える。

まぁ、下のツイート(俺じゃないよ)の内容を詳しく書き起こしたようなものなので、このツイートを読んで納得した人は続きを読まなくても良いかもしれない……。

累積××の適用方法には2つのパターンが存在する。以下、パターン1・2と勝手に名付ける。

パターン1:逆演算が定義できなくても、「最初から途中まで」の累積演算ができる

逆演算っていうの?逆作用素なの?……それがない場合には、「最初から途中まで」の累積演算ができる。

\oplusを一般の(何らかの)演算子とする。 例えば累積和の場合は\oplusは+(加算演算子)だし、累積XORの場合はXOR(ビットXOR、ビットごとの排他的論理和)である*1

で、a_1 \oplus a_2 \oplus a_3 \oplus ... \oplus a_nを求めることを考える。 このとき、

b_1 = a_1
b_2 = a_1 \oplus a_2
b_3 = a_1 \oplus a_2 \oplus a_3
\cdots

で定まるb_nを考えよう。b_k = b_{k-1} \oplus a_k と計算することで、b_1からb_nまでを計算時間O(n)で計算できる。
そうすると、b_nを何度も使う場合には計算量が削減できる。

逆側からの累積と合わせ技

GCD on Blackboard問題の場合は上記のb_nだけでは不十分である。
c_1 = a_n
c_2 = a_{n-1} \oplus a_{n}
c_3 = a_{n-2} \oplus a_{n-1} \oplus a_{n}
\cdots

で定まる「逆側からの累積GCD」も同時に考える必要がある。こちらは「途中から最後まで」の累積演算になる。

これはどういう演算子に対して使用可能か?

GCD on Blackboardの解説PDFには登場しているのだが、これをするためには演算子結合法則
 (a_1 \oplus a_2) \oplus a_3 = a_1 \oplus (a_2 \oplus a_3)
を満たしている必要がある。

簡単に言えば、gcdには「どこから計算しても結果は変わらない性質」があります
GCD on Blackboardの解説PDF

gcdはこれを満たしている。 すなわち、gcd(gcd(a1, a2), a3)= gcd(a1, gcd(a2, a3)) が成り立つ。

他には……bitのand / or、最大値/最小値なんかは結合法則を満たす。行列積も結合法則を満たす。 一方、減算や除算は結合法則を満たさないので、この手法が使えない。

そうすると……適当に作った問題だけど、こんな感じかな。(問題の細かい条件は割愛)

黒板に1列に数列a1, a2, …… , aNが書いてあります。
これに対して、
s1, e1
s2, e2
……
sM, eM
という入力が与えられます。
M個の数を出力しなさい。
i番目(1 ≦ i ≦ M)の出力では、
数列が全て書いてある状態から、si番目からei番目までの数を消したときに、黒板に残った数の最大値を出力しなさい。

とかいう問題が出てきたら、累積最大値を使えば計算量O(N+M)で解けそうだ。(GCD on Blackboardとほとんど同様にして解ける。)

パターン2:逆演算があれば、「任意の連続部分」の累積演算ができる

パターン1ではbnとcn、すなわち「最初から途中まで」「途中から最後まで」だけを計算できた。 「途中から途中まで」すなわち「任意の連続部分」に関して、累積の演算結果を求められるのがパターン2である。
パターン1の特別な場合がパターン2である。

\oplus の逆演算 \ominus が存在する場合、例えば
a_3 \oplus a_4 \oplus a_5 が必要だったら、
b_5 \ominus b_2 = (a_1 \oplus a_2 \oplus a_3 \oplus a_4 \oplus a_5) \ominus (a_1 \oplus a_2) = a_3 \oplus a_4 \oplus a_5
とすればよい。一般の連続区間についても同様である。

パターン2では単にb_nだけを先に計算すればよく、c_nはもはや必要ない(b_nから直ちに計算できる)。

これはどういう演算子に対して使用可能か?

このパターンは逆演算 (逆作用素)があることが必要である。

直前の書き方は、実は正確ではない(一般の演算子について成り立たない)。 正しい求め方は、eを演算に対する単位元として

 e \ominus b_2 \oplus b_5  = e \ominus (a_1 \oplus a_2) \oplus (a_1 \oplus a_2 \oplus a_3 \oplus a_4 \oplus a_5)
 = e \ominus (a_1 \oplus a_2) \oplus (a_1 \oplus a_2) \oplus (a_3 \oplus a_4 \oplus a_5)  (結合法則より)
 = e \oplus (a_3 \oplus a_4 \oplus a_5)  (逆演算の性質より)
 = a_3 \oplus a_4 \oplus a_5  (単位元の性質より)

だと思う。

GCDはこの性質を満たさない(逆演算子が存在しない)ので、パターン2の解法は使えない。

累積和(加算)の場合は  \ominus が-になる。 XORの場合は  \ominus もXORである。(a XOR b) XOR b = aが成り立つからだ。

累積XORについては 競技プログラミングにおけるXORのTips - Qiita でも紹介されている。

あとは上のツイートにもあったが、積(乗算)が同様にできるのも分かるだろう(ただし0との乗算は除外する)。

他に同様の性質を満たす演算はあるだろうか?

結合法則単位元・逆元を満たす演算を、代数学ではと呼ぶ。
群 (数学) - Wikipediaを見ながら思い出すと、例えば、2次正方行列で逆行列が存在するものの集合は群となる。

よって、そのような行列A_iに対して、例えば  A_3 A_4 A_5を計算したい場合、  B_n = A_1 A_2 \cdots A_nをあらかじめ計算しておけばB5にB2の逆元を左からかけることで

 B_2^{-1} B_5 = (A_1 A_2)^{-1}(A_1 A_2 A_3 A_4 A_5) = A_3 A_4 A_5 となって求めることができる。

なお、行列乗算は交換法則を満たさないので、右から乗算すると失敗する。(上で「実は正確ではない(一般の演算子について成り立たない)」とか書いていたのは、交換法則を満たさない場合の話である。)

……だけどこんな行列積の計算、競技プログラミングにも絶対出てこないだろ。

というわけで今日はここまで。
それでは。

*1:排他的論理和 - Wikipediaを見たらXORの代わりにこの記号を使う場合もあるらしい。マジか。紛らわしくてごめんなさい、今回はXORと無関係な一般の演算子のことを指している