imos法
配列の長さをNとする.imos法により,区間更新を複数回行った後の,配列の各要素の値を得ることができる.操作の時間計算量は以下.
- 区間更新: O(1)
- 配列の各要素の値の取得O(N)
配列の各要素は群を成す集合の要素である必要がある.
説明
群における2項演算を仮に和として考える.
imos法でいう区間更新とは,配列のある区間の全要素に同じ値を足す操作のことである.これは,愚直に行うとO(N)かかってしまう.
imos法は,どの区間に何の値を足す必要があるかという情報を覚えておき,配列の要素を最後に取得する時に,それまでの区間更新をまとめて処理するという方法である.より具体的に説明すると,区間更新操作では,左から累積和(O(N))を取った時に,配列の各要素の値を得られるように値の更新を行う.
最後に左から累積和を取ることを前提として考えると,配列のある要素にある値を足すことは,その要素以降の全要素にその値を足すことを意味する.
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
クエリ1 | - | +1 | +1 | +1 | +1 | +1 | +1 | +1 |
更新1 | 0 | +1 | 0 | 0 | 0 | 0 | 0 | 0 |
累積和 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
よって,例えばある区間に値xを足す場合,区間クエリの先頭にxを足し,終わりに−xを足すことで,最後に累積和を取った時にその区間の要素のみにxを足すことができる.
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
クエリ1 | - | +x | +x | +x | +x | - | - | - |
更新1 | 0 | +x | 0 | 0 | 0 | −x | 0 | 0 |
累積和 | 0 | x | x | x | x | 0 | 0 | 0 |
区間更新が複数あっても操作は変わらない.
Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
クエリ1 | - | +x | +x | +x | +x | - | - | - |
更新1 | 0 | +x | 0 | 0 | 0 | −x | 0 | 0 |
クエリ2 | - | - | +y | +y | +y | +y | - | - |
更新2 | 0 | +x | +y | 0 | 0 | −x | −y | 0 |
クエリ3 | - | - | - | +z | +z | - | - | - |
更新3 | 0 | +x | +y | +z | 0 | −x−z | −y | 0 |
累積和 | 0 | x | x+y | x+y+z | x+y+z | y | 0 | 0 |
扱う代数系を群一般としてまとめると,区間[l,r))に値\(xを作用させる場合,配列のl番目にxを作用させ,r番目にxの逆元を作用させる.実際の値を得るには,左から累積値を取れば良い.
コード
template <class S, S (*op)(S, S), S (*e)(), S (*inv)(S)> struct Imos {
private:
vector<S> v;
public:
Imos(int size) { v.assign(size, e()); }
void query(int l, int r, S x) { // [l, r)
v[l] = op(v[l], x);
v[r] = op(v[r], inv(x));
}
vector<S> get_values() {
rep (i, 1, v.size()) v[i] = op(v[i-1], v[i]);
return move(v);
}
};