Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions LazySegTree/LazySegTree.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/**
* TODO: verify {@link LazySegTree#maxRight} and {@link LazySegTree#minLeft}
*
* @verified https://atcoder.jp/contests/practice2/tasks/practice2_k
*/
class LazySegTree<S, F> {
final int MAX;

final int N;
final int Log;
final java.util.function.BinaryOperator<S> Op;
final S E;
final java.util.function.BiFunction<F, S, S> Mapping;
final java.util.function.BinaryOperator<F> Composition;
final F Id;

final S[] Dat;
final F[] Laz;

@SuppressWarnings("unchecked")
public LazySegTree(int n, java.util.function.BinaryOperator<S> op, S e, java.util.function.BiFunction<F, S, S> mapping, java.util.function.BinaryOperator<F> composition, F id) {
this.MAX = n;
int k = 1;
while (k < n) k <<= 1;
this.N = k;
this.Log = Integer.numberOfTrailingZeros(N);
this.Op = op;
this.E = e;
this.Mapping = mapping;
this.Composition = composition;
this.Id = id;
this.Dat = (S[]) new Object[N << 1];
this.Laz = (F[]) new Object[N];
java.util.Arrays.fill(Dat, E);
java.util.Arrays.fill(Laz, Id);
}

public LazySegTree(S[] dat, java.util.function.BinaryOperator<S> op, S e, java.util.function.BiFunction<F, S, S> mapping, java.util.function.BinaryOperator<F> composition, F id) {
this(dat.length, op, e, mapping, composition, id);
build(dat);
}

private void build(S[] dat) {
int l = dat.length;
System.arraycopy(dat, 0, Dat, N, l);
for (int i = N - 1; i > 0; i--) {
Dat[i] = Op.apply(Dat[i << 1 | 0], Dat[i << 1 | 1]);
}
}

private void push(int k) {
if (Laz[k] == Id) return;
int lk = k << 1 | 0, rk = k << 1 | 1;
Dat[lk] = Mapping.apply(Laz[k], Dat[lk]);
Dat[rk] = Mapping.apply(Laz[k], Dat[rk]);
if (lk < N) Laz[lk] = Composition.apply(Laz[k], Laz[lk]);
if (rk < N) Laz[rk] = Composition.apply(Laz[k], Laz[rk]);
Laz[k] = Id;
}

private void pushTo(int k) {
for (int i = Log; i > 0; i--) push(k >> i);
}

private void pushTo(int lk, int rk) {
for (int i = Log; i > 0; i--) {
if (((lk >> i) << i) != lk) push(lk >> i);
if (((rk >> i) << i) != rk) push(rk >> i);
}
}

private void updateFrom(int k) {
k >>= 1;
while (k > 0) {
Dat[k] = Op.apply(Dat[k << 1 | 0], Dat[k << 1 | 1]);
k >>= 1;
}
}

private void updateFrom(int lk, int rk) {
for (int i = 1; i <= Log; i++) {
if (((lk >> i) << i) != lk) {
int lki = lk >> i;
Dat[lki] = Op.apply(Dat[lki << 1 | 0], Dat[lki << 1 | 1]);
}
if (((rk >> i) << i) != rk) {
int rki = (rk - 1) >> i;
Dat[rki] = Op.apply(Dat[rki << 1 | 0], Dat[rki << 1 | 1]);
}
}
}

public void set(int p, S x) {
exclusiveRangeCheck(p);
p += N;
pushTo(p);
Dat[p] = x;
updateFrom(p);
}

public S get(int p) {
exclusiveRangeCheck(p);
p += N;
pushTo(p);
return Dat[p];
}

public S prod(int l, int r) {
if (l > r) {
throw new IllegalArgumentException(
String.format("Invalid range: [%d, %d)", l, r)
);
}
inclusiveRangeCheck(l);
inclusiveRangeCheck(r);
if (l == r) return E;
l += N; r += N;
pushTo(l, r);
S sumLeft = E, sumRight = E;
while (l < r) {
if ((l & 1) == 1) sumLeft = Op.apply(sumLeft, Dat[l++]);
if ((r & 1) == 1) sumRight = Op.apply(Dat[--r], sumRight);
l >>= 1; r >>= 1;
}
return Op.apply(sumLeft, sumRight);
}

public S allProd() {
return Dat[1];
}

public void apply(int p, F f) {
Dat[p] = Mapping.apply(f, get(p));
updateFrom(p + N);
}

public void apply(int l, int r, F f) {
if (l > r) {
throw new IllegalArgumentException(
String.format("Invalid range: [%d, %d)", l, r)
);
}
inclusiveRangeCheck(l);
inclusiveRangeCheck(r);
if (l == r) return;
l += N; r += N;
pushTo(l, r);
for (int l2 = l, r2 = r; l2 < r2;) {
if ((l2 & 1) == 1) {
Dat[l2] = Mapping.apply(f, Dat[l2]);
if (l2 < N) Laz[l2] = Composition.apply(f, Laz[l2]);
l2++;
}
if ((r2 & 1) == 1) {
r2--;
Dat[r2] = Mapping.apply(f, Dat[r2]);
if (r2 < N) Laz[r2] = Composition.apply(f, Laz[r2]);
}
l2 >>= 1; r2 >>= 1;
}
updateFrom(l, r);
}

public int maxRight(int l, java.util.function.Predicate<S> g) {
inclusiveRangeCheck(l);
if (!f.test(E)) {
throw new IllegalArgumentException("Identity element must satisfy the condition.");
}
if (l == MAX) return MAX;
l += N;
pushTo(l);
S sum = E;
do {
l >>= Integer.numberOfTrailingZeros(l);
if (!g.test(Op.apply(sum, Dat[l]))) {
while (l < N) {
push(l);
l = l << 1;
if (g.test(Op.apply(sum, Dat[l]))) {
sum = Op.apply(sum, Dat[l]);
l++;
}
}
return l - N;
}
sum = Op.apply(sum, Dat[l]);
l++;
} while ((l & -l) != l);
return MAX;
}

public int minLeft(int r, java.util.function.Predicate<S> g) {
inclusiveRangeCheck(r);
if (!f.test(E)) {
throw new IllegalArgumentException("Identity element must satisfy the condition.");
}
if (r == 0) return 0;
r += N;
pushTo(r - 1);
S sum = E;
do {
r--;
while (r > 1 && (r & 1) == 1) r >>= 1;
if (!g.test(Op.apply(Dat[r], sum))) {
while (r < N) {
push(r);
r = r << 1 | 1;
if (g.test(Op.apply(Dat[r], sum))) {
sum = Op.apply(Dat[r], sum);
r--;
}
}
return r + 1 - N;
}
sum = Op.apply(Dat[r], sum);
} while ((r & -r) != r);
return 0;
}

private void exclusiveRangeCheck(int p) {
if (p < 0 || p >= MAX) {
throw new IndexOutOfBoundsException(
String.format("Index %d is not in [%d, %d).", p, 0, MAX)
);
}
}

private void inclusiveRangeCheck(int p) {
if (p < 0 || p > MAX) {
throw new IndexOutOfBoundsException(
String.format("Index %d is not in [%d, %d].", p, 0, MAX)
);
}
}
}
160 changes: 160 additions & 0 deletions LazySegTree/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# クラス LazySegTree

[モノイド](https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8E%E3%82%A4%E3%83%89) `(S,⋅:S×S→S,e∈S)` と、`S` から `S` への写像の集合 `F` であって、以下の条件を満たすようなものについて使用できるデータ構造です。

- `F` は恒等写像 `id` を含む。つまり、任意の $x∈S$ に対し `id(x) = x` をみたす。
- `F` は写像の合成について閉じている。つまり、任意の `f,g∈F` に対し `f∘g∈F` である。
- 任意の `f∈F,x,y∈S` に対し `f(x⋅y)=f(x)⋅f(y)` をみたす。

長さ `N` の `S` の配列に対し、

- 区間の要素に一括で `F` の要素 `f` を作用 (`x = f(x)`)
- 区間の要素の総積の取得

を $O(\log N)$ で行うことが出来ます。

また、このライブラリはオラクルとして `op`, `mapping`, `composition` を使用しますが、これらが定数時間で動くものと仮定したときの計算量を記述します。オラクル内部の計算量が $O(f(n))$ である場合はすべての計算量が $O(f(n))$ 倍となります

## コンストラクタ

引数の意味は以下の通りです

- `S` : モノイドの型
- `F` : 写像の型
- `op` : `⋅:S×S→S` を計算する関数 S
- `e` : モノイドの単位元
- `mapping`: `f(x)` を返す関数
- `composition` : `f∘g` を返す関数
- `id` : 恒等写像

```java
public LazySegTree<S, F>(int n, java.util.function.BinaryOperator<S> op, S e, java.util.function.BiFunction<F, S, S> mapping, java.util.function.BinaryOperator<F> composition, F id)
```

長さ `n` の配列 `a[0], a[1], ..., a[n - 1]` を作ります. 初期値はすべて $e$ です.

計算量: $O(n)$

```java
public LazySegTree<S, F>(S[] dat, java.util.function.BinaryOperator<S> op, S e, java.util.function.BiFunction<F, S, S> mapping, java.util.function.BinaryOperator<F> composition, F id)
```

長さ `n` の配列 `a[0], a[1], ..., a[n - 1]` を `dat` により初期化します.

計算量: $O(n)$

## メソッド

### set

```java
public void set(int p, S x)
```

`a[p]=x` とします.

計算量: $O(\log n)$

制約: `0 <= p < n`

### get

```java
public S get(int p)
```

`a[p]` を取得します.

計算量: $O(\log n)$

制約: `0 <= p < n`

### prod

```java
public S prod(int l, int r)
```

`op(a[l], ..., a[r - 1])` を、モノイドの性質を満たしていると仮定して計算します。`l = r` のときは単位元 `e` を返します。

計算量: $O(n)$

制約: `0 <= l <= r <= n`

### allProd

```java
public S allProd()
```

`op(a[0], ..., a[n - 1])` を、モノイドの性質を満たしていると仮定して計算します。`n = 0` のときは単位元 `e` を返します。

計算量: $O(1)$

### apply

```java
// (1)
public void apply(int p, F f)
// (2)
public void apply(int l, int r, F f)
```

- (1): `a[p]` に作用素 `f` を作用させます
- (2): `i∈[l, r)` に対して `a[i]` に作用素 `f` を作用させます

制約

- (1): `0 <= p < n`
- (2): `0 <= l <= r <= n`

計算量

$O(\log n)$

### maxRight

```java
public int maxRight(int l, java.util.function.Predicate<S> f)
```

`S` を引数にとり `boolean` を返す関数を渡して使用します。
以下の条件を両方満たす `r` を (いずれか一つ) 返します。

- `r = l` もしくは `f(op(a[l], a[l + 1], ..., a[r - 1])) = true`
- `r = n` もしくは `f(op(a[l], a[l + 1], ..., a[r])) = false`

`f` が単調だとすれば、`f(op(a[l], a[l + 1], ..., a[r - 1])) = true` となる最大の `r`、と解釈することが可能です。

制約

- `f` を同じ引数で呼んだ時、返り値は等しい(=副作用はない)
- __`f(e) = true`__
- `0 <= l <= n`
計算量

$O(\log n)$

### minLeft

```java
public int minLeft(int r, java.util.function.Predicate<S> f)
```

`S` を引数にとり `boolean` を返す関数オブジェクトを渡して使用します。
以下の条件を両方満たす `l` を (いずれか一つ) 返します。

- `l = r` もしくは `f(op(a[l], a[l + 1], ..., a[r - 1])) = true`
- `l = 0` もしくは `f(op(a[l - 1], a[l + 1], ..., a[r - 1])) = false`

fが単調だとすれば、`f(op(a[l], a[l + 1], ..., a[r - 1])) = true` となる最小の `l`、と解釈することが可能です。

制約

- `f` を同じ引数で呼んだ時、返り値は等しい(=副作用はない)
- `f(e) = true`
- `0 <= r <= n`

計算量

$O(\log n)$
Loading