---
tags: 競プロ
---
# お手軽非再帰 Segment Tree の書き方
注意 : ここに書いたコードの verify はしていません
### 長さ $2n$ のセグ木にする
> ![](https://i.imgur.com/WVKCSsq.png)
>
> [非再帰セグ木サイコー! 一番すきなセグ木です p.35](https://hcpc-hokudai.github.io/archive/structure_segtree_001.pdf)
```cpp!
struct SegmentTree {
int n;
vector<T> seg;
SegmentTree(int n): n(n), seg(n * 2) {}
}
```
* $\text{seg}[n], …, \text{seg}[2n - 1]$ が区間 $[0, 1), …, [n - 1, n)$ に対応します。
* $\text{seg}[i]$ は $\text{seg}[2i]$ と $\text{seg}[2i + 1]$ をくっつけたものです。
* $\text{seg}[0]$ は使いません。
* $2$ 冪のときと違って、$\text{seg}[1] = a_5 \otimes \dots \otimes a_{n-1} \otimes a_0 \otimes \dots \otimes a_4$ のようになるので、$a_0 \otimes \dots \otimes a_{n-1}$ が欲しい場合は $\Theta(\log n)$ 時間かける必要があります。(可換なら $\text{seg}[1]$ で良い)
### 1 点取得
```cpp!
T operator[](int i) const {
return seg[n + i];
}
```
- $\text{seg}[n + i]$ が $a_i$ に対応するので、$\text{seg}[n + i]$ を返します。
- 短いので、混乱を避けるために複数回 1 点取得するなら書いておきましょう。
- `const` は `const SegmentTree` 型のときにも使えるように書きます。(JOI なら要らないかも)
### 1 点代入
```cpp!
void set(int i, T x) {
i += n;
seg[i] = x;
while(i >>= 1) seg[i] = op(seg[i << 1], seg[i << 1 | 1]);
}
```
- $\text{seg}[n + i]$ が $a_i$ に対応するので、$\text{seg}[n + i]$ に代入して上を更新します。
```cpp!
void add(int i, T x) {
i += n;
do seg[i] += x; while(i >>= 1);
}
void set(int i, T x) {
add(i, x - seg[n + i]);
}
```
- 区間和を持つセグ木なら、1 点代入より 1 点加算の方が簡単です。
### 区間取得
```cpp!
void prod(int l, int r) const {
T ansL = e(), ansR = e();
for(l += n, r += n; l < r; l >>= 1, r >>= 1) {
if(l & 1) ansL = op(ansL, seg[l++]);
if(r & 1) ansR = op(seg[--r], ansR);
}
return op(ansL, ansR);
}
```
- 初めに $n$ を足し、上がりながら区間を狭めていきます。
- 上がるときは $2$ で割れないといけないので、$2$ で割れないときに狭めます。
- 非可換な場合は、左右に気をつける必要があります。
```cpp!
void prod(int l, int r) const {
T ans = e();
for(l += n, r += n; l < r; l >>= 1, r >>= 1) {
if(l & 1) ans = op(ans, seg[l++]);
if(r & 1) ans = op(ans, seg[--r]);
}
return ans;
}
```
- 可換な場合は、実家のような安心感
### max-right
```cpp!
template<class F> int max_right(int L, F f) const {
int l = n + L, w = 1, ansL = e();
for(; L + w <= n; l >>= 1, w <<= 1) if(l & 1) {
if(not f(op(ansL, seg[l]))) break;
ansL = op(ansL, seg[l++]);
L += w;
}
while(l <<= 1, w >>= 1){
if(L + w <= n && f(op(ans, seg[l]))){
ansL = op(ansL, seg[l++]);
L += w;
}
}
return L;
}
```
- `template<class F> F f` でラムダ式など全てを受け取れます。
- セグ木上の位置 `l` と列上の位置 `L` を両方持ちます。
- 現在のセグ木上の位置で $1$ 動かすと列上の位置がどれだけ動くかがわからないので、その変数 `w` を持ちます。
- 上がりながら動かし、ダメになったら下がりながら動かします。
- `L` が `n` を超えないように常に気をつけます。
- `w` が $0$ になったらおわりです。
### min-left
```cpp!
template<class F> int min_left(int R, F f) const {
int r = n + R, w = 1, ansR = e();
for(; R - w >= 0; r >>= 1, w <<= 1) if(r & 1) {
if(not f(op(seg[r - 1], ansR))) break;
ansR = op(seg[--r], ansR);
R -= w;
}
while(r <<= 1, w >>= 1){
if(R - w >= 0 && f(op(seg[r - 1], ansR))){
ansR = op(seg[--r], ansR);
R -= w;
}
}
return R;
}
```
- 対称な実装で OK!
- `R` が `0` を超えないように常に気をつけます。
- `w` を持っているので `r = 2` から `r = 1` へ **上がって** 困ることはありません。(上がってもすぐキャンセルされる)