Skip to content

Commit

Permalink
Merge pull request #73 from BRUTEUdesc/joao
Browse files Browse the repository at this point in the history
segtrees melhoradas
  • Loading branch information
joaomarcosth9 authored Feb 21, 2024
2 parents c91d992 + 2e14434 commit 8c000ee
Show file tree
Hide file tree
Showing 31 changed files with 512 additions and 279 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
template <typename T> struct FenwickTree {
int n;
vector<T> bit, arr;
FenwickTree(int n = 0) : n(n), bit(n), arr(n) { }
FenwickTree(vector<T> &v) : n(v.size()), bit(n), arr(v) {
FenwickTree(int _n = 0) : n(_n), bit(n), arr(n) { }
FenwickTree(vector<T> &v) : n(int(v.size())), bit(n), arr(v) {
for (int i = 0; i < n; i++) {
bit[i] = arr[i];
}
Expand Down
4 changes: 2 additions & 2 deletions Codigos/Estruturas-de-Dados/LiChao-Tree/lichao_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ template <ll MINL = ll(-1e9 - 5), ll MAXR = ll(1e9 + 5)> struct LichaoTree {
tree.push_back(Line());
L.push_back(-1);
R.push_back(-1);
return int(tree.size() - 1);
return int(tree.size()) - 1;
}

LichaoTree() { newnode(); }
Expand Down Expand Up @@ -62,4 +62,4 @@ template <ll MINL = ll(-1e9 - 5), ll MAXR = ll(1e9 + 5)> struct LichaoTree {
return max(tree[n](x), query(x, ri(n), mid + 1, r));
}
}
};
};
2 changes: 1 addition & 1 deletion Codigos/Estruturas-de-Dados/Operation-Queue/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# [Operation Queue](op_queue.cpp)

Fila que armazena o resultado do operatório dos itens (ou seja, dado uma fila, responde qual é o elemento mínimo, por exemplo). É uma extensão da $std::queue$, permitindo todos os métodos já presentes nela, com a diferença de que $push$ e $pop$ agora são $add$ e $remove$, respectivamente, ambos continuam $\mathcal{O}(1)$ amortizado. A fila agora também permite a operação $get$ que retorna o resultado do operatório dos itens da fila em $\mathcal{O}(1)$ amortizado. Chamar o método $get$ em uma fila vazia é indefinido.
Fila que armazena o resultado do operatório dos itens (ou seja, dado uma fila, responde qual é o elemento mínimo, por exemplo). É uma extensão da `std::queue`, permitindo todos os métodos já presentes nela, com a diferença de que `push` e `pop` agora são `add` e `remove`, respectivamente, ambos continuam $\mathcal{O}(1)$ amortizado. A fila agora também permite a operação `get` que retorna o resultado do operatório dos itens da fila em $\mathcal{O}(1)$ amortizado. Chamar o método `get` em uma fila vazia é indefinido.

**Obs**: usa a estrutura Operation Stack.
2 changes: 1 addition & 1 deletion Codigos/Estruturas-de-Dados/Operation-Stack/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# [Operation Stack](op_stack.cpp)

Pilha que armazena o resultado do operatório dos itens (ou seja, dado uma pilha, responde qual é o elemento mínimo, por exemplo). É uma extensão da $std::stack$, permitindo todos os métodos já presentes nela, com a diferença de que $push$ e $pop$ agora são $add$ e $remove$, respectivamente, ambos continuam $\mathcal{O}(1)$ amortizado. A pilha agora também permite a operação $get$ que retorna o resultado do operatório dos itens da pilha em $\mathcal{O}(1)$ amortizado. Chamar o método $get$ em uma pilha vazia é indefinido.
Pilha que armazena o resultado do operatório dos itens (ou seja, dado uma pilha, responde qual é o elemento mínimo, por exemplo). É uma extensão da `std::stack`, permitindo todos os métodos já presentes nela, com a diferença de que `push` e `pop` agora são `add` e `remove`, respectivamente, ambos continuam $\mathcal{O}(1)$ amortizado. A pilha agora também permite a operação `get` que retorna o resultado do operatório dos itens da pilha em $\mathcal{O}(1)$ amortizado. Chamar o método `get` em uma pilha vazia é indefinido.
20 changes: 10 additions & 10 deletions Codigos/Estruturas-de-Dados/Ordered-Set/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@

Set com operações de busca por ordem e índice.

Pode ser usado como um $std::set$ normal, a principal diferença são duas novas operações possíveis:
Pode ser usado como um `std::set` normal, a principal diferença são duas novas operações possíveis:

- $find\_by\_order(k)$: retorna um iterador para o k-ésimo menor elemento no set (indexado em 0).
- $order\_of\_key(k)$: retorna o número de elementos menores que k. (ou seja, o índice de k no set)
- `find_by_order(k)`: retorna um iterador para o $k$-ésimo menor elemento no set (indexado em 0).
- `order_of_key(k)`: retorna o número de elementos menores que $k$. (ou seja, o índice de $k$ no set)

Ambas as operações são $\mathcal{O}(log(n))$.

Também é possível criar um $ordered\_map$, funciona como um $std::map$, mas com as operações de busca por ordem e índice. $find\_by\_order(k)$ retorna um iterador para a k-ésima menor **key** no mapa (indexado em 0). $order\_of\_key(k)$ retorna o número de **keys** no mapa menores que k. (ou seja, o índice de k no map).
Também é possível criar um `ordered_map`, funciona como um `std::map`, mas com as operações de busca por ordem e índice. `find_by_order(k)` retorna um iterador para a $k$-ésima menor **key** no mapa (indexado em 0). `order_of_key(k)` retorna o número de **keys** no mapa menores que $k$. (ou seja, o índice de $k$ no map).

Para simular um $std::multiset$, há várias formas:
Para simular um `std::multiset`, há várias formas:

- Usar um $std::pair$ como elemento do set, com o primeiro elemento sendo o valor e o segundo sendo um identificador único para cada elemento. Para saber o número de elementos menores que $k$ no multiset, basta usar $order\_of\_key({k, -INF})$.
- Usar um `std::pair` como elemento do set, com o primeiro elemento sendo o valor e o segundo sendo um identificador único para cada elemento. Para saber o número de elementos menores que $k$ no multiset, basta usar `order_of_key({k, -INF})`.

- Usar um $ordered\_map$ com a key sendo o valor e o value sendo o número de ocorrências do valor no multiset. Para saber o número de elementos menores que $k$ no multiset, basta usar $order\_of\_key(k)$.
- Usar um `ordered_map` com a key sendo o valor e o value sendo o número de ocorrências do valor no multiset. Para saber o número de elementos menores que $k$ no multiset, basta usar `order_of_key(k)`.

- Criar o $ordered\_set$ substituindo o parâmetro $less<T>$ por $less\_equal<T>$. Isso faz com que o set aceite elementos repetidos, e $order\_of\_key(k)$ retorna o número de elementos menores ou iguais a $k$ no multiset. Porém esse método não é recomendado pois gera algumas inconsistências, como por exemplo: $upper\_bound$ funciona como $lower\_bound$ e vice-versa, $find$ sempre retorna $end()$ e $erase$ por valor não funciona, só por iterador. Dá pra usar se souber o que está fazendo.
- Criar o `ordered_set` trocando o parâmetro `less<T>` por `less_equal<T>`. Isso faz com que o set aceite elementos repetidos, e `order_of_key(k)` retorna o número de elementos menores ou iguais a `k` no multiset. Porém esse método não é recomendado pois gera algumas inconsistências, como por exemplo: `upper_bound` funciona como `lower_bound` e vice-versa, `find` sempre retorna `end()` e `erase` por valor não funciona, só por iterador. Dá pra usar se souber o que está fazendo.

Exemplo de uso do $ordered\_set$:
Exemplo de uso do `ordered_set`:

```cpp
ordered_set<int> X;
Expand All @@ -39,7 +39,7 @@ cout << X.order_of_key(4) << endl; // 2
cout << X.order_of_key(400) << endl; // 5
```

Exemplo de uso do $ordered\_map$:
Exemplo de uso do `ordered_map`:

```cpp
ordered_map<int, int> Y;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# [Seg Tree 2D](seg_tree.cpp)
# [Segment Tree 2D](seg_tree.cpp)

Segment Tree em 2 dimensões.

- Complexidade de tempo (Pré-processamento): $\mathcal{O}(N \cdot M)$
- Complexidade de tempo (Consulta em intervalo): $\mathcal{O}(\log N \cdot \log M)$
- Complexidade de tempo (Update em ponto): $\mathcal{O}(\log N \cdot \log M)$
- Complexidade de espaço: $4 * N \cdot 4 * M = \mathcal{O}(N \cdot M)$
Segment Tree em 2 dimensões, suporta operações de update pontual e consulta em intervalo. A construção é $\mathcal{O}(n \cdot m)$ e as operações de consulta e update são $\mathcal{O}(log(n) \cdot log(m))$.
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
# [Seg Tree Beats Max and Sum update](seg_tree_beats_max_and_sum_update.cpp)
# [Segment Tree Beats 2](seg_tree_beats_max_and_sum_update.cpp)

Seg Tree que suporta update de maximo, update de soma e query de soma.
Utiliza uma fila de lazy para diferenciar os updates

- Complexidade de tempo (Pré-processamento): $\mathcal{O}(N)$
- Complexidade de tempo (Consulta em intervalo): $\mathcal{O}(log(N))$
- Complexidade de tempo (Update em ponto): $\mathcal{O}(log(N))$
- Complexidade de tempo (Update em intervalo): $\mathcal{O}(log(N))$
- Complexidade de espaço: 2 * 4 * N = $\mathcal{O}(N)$
Segment Tree que suporta update de maximo, update de soma e query de soma. Utiliza uma fila de lazy para diferenciar os updates. A construção é $\mathcal{O}(n)$ e as operações de consulta e update são $\mathcal{O}(log(n))$.
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# [Seg Tree Beats](seg_tree_beats.cpp)
Seg Tree que suporta update de maximo e query de soma
# [Segment Tree Beats](seg_tree_beats.cpp)

- Complexidade de tempo (Pré-processamento): $\mathcal{O}(N)$
- Complexidade de tempo (Consulta em intervalo): $\mathcal{O}(log(N))$
- Complexidade de tempo (Update em ponto): $\mathcal{O}(log(N))$
- Complexidade de tempo (Update em intervalo): $\mathcal{O}(log(N))$
- Complexidade de espaço: $2 * 4 * N = \mathcal{O}(N)$
Segment Tree que suporta update de maximo e query de soma. A construção é $\mathcal{O}(n)$ e as operações de consulta e update são $\mathcal{O}(log(n))$.
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
# [Sparse Seg Tree](seg_tree_sparse.cpp)
# [Segment Tree Esparsa](seg_tree_sparse.cpp)

Seg Tree Esparsa, ou seja, uma seg tree que não guarda todos os nós, mas apenas os nós que são necessários para responder as queries, permitindo fazer queries em intervalos de tamanho arbitrário.

Seja $LEN$ o tamanho do intervalo em que a Seg Tree foi construída:

- Complexidade de tempo (Pré-processamento): $\mathcal{O}(1)$
- Complexidade de tempo (Consulta em intervalo): $\mathcal{O}(log(LEN))$
- Complexidade de tempo (Update em ponto): $\mathcal{O}(log(LEN))$
Segment Tree Esparsa, ou seja, não armazena todos os nós da árvore, apenas os necessários, dessa forma ela suporta operações em intervalos arbitrários. A construção é $\mathcal{O}(1)$ e as operações de consulta e update são $\mathcal{O}(log(L))$, onde $L$ é o tamanho do intervalo. A implementação suporta operações de consulta em intervalo e update pontual. Está implementada para soma, mas pode ser facilmente modificada para outras operações.
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
const int SEGMAX = 8e6 + 5; // should be Q * log(DIR-ESQ+1)
const ll ESQ = 0, DIR = 1e9 + 7;
template <ll MINL = ll(-1e9 - 5), ll MAXR = ll(1e9 + 5)> struct SegTree {
const ll neutral = 0;
struct node {
ll val;
int L, R;
node(ll v) : val(v), L(-1), R(-1) { }
};
ll merge(ll a, ll b) { return a + b; }

struct seg {
ll tree[SEGMAX];
int R[SEGMAX], L[SEGMAX],
ptr = 2; // 0 is NULL; 1 is First Root
ll op(ll a, ll b) { return (a + b) % MOD; }
int le(int i) {
if (L[i] == 0) {
L[i] = ptr++;
vector<node> t;

int newnode() {
t.push_back(node(neutral));
return int(t.size() - 1);
}

SegTree() { newnode(); }

int le(int u) {
if (t[u].L == -1) {
t[u].L = newnode();
}
return L[i];
return t[u].L;
}
int ri(int i) {
if (R[i] == 0) {
R[i] = ptr++;

int ri(int u) {
if (t[u].R == -1) {
t[u].R = newnode();
}
return R[i];
return t[u].R;
}
ll query(ll l, ll r, int n = 1, ll esq = ESQ, ll dir = DIR) {
if (r < esq || dir < l) {
return 0;

ll query(int u, ll l, ll r, ll L, ll R) {
if (l > R || r < L) {
return neutral;
}
if (l <= esq && dir <= r) {
return tree[n];
if (l >= L && r <= R) {
return t[u].val;
}
ll mid = (esq + dir) / 2;
return op(query(l, r, le(n), esq, mid), query(l, r, ri(n), mid + 1, dir));
ll mid = l + (r - l) / 2;
ll ql = query(le(u), l, mid, L, R);
ll qr = query(ri(u), mid + 1, r, L, R);
return merge(ql, qr);
}
void update(ll x, ll v, int n = 1, ll esq = ESQ, ll dir = DIR) {
if (esq == dir) {
tree[n] = (tree[n] + v) % MOD;
ll query(ll l, ll r) { return query(0, MINL, MAXR, l, r); }

void update(int u, ll l, ll r, ll i, ll x) {
debug(u, l, r);
if (l == r) {
t[u].val += x; // soma
// t[u].val = x; // substitui
return;
}
ll mid = l + (r - l) / 2;
if (i <= mid) {
update(le(u), l, mid, i, x);
} else {
ll mid = (esq + dir) / 2;
if (x <= mid) {
update(x, v, le(n), esq, mid);
} else {
update(x, v, ri(n), mid + 1, dir);
}
tree[n] = op(tree[le(n)], tree[ri(n)]);
update(ri(u), mid + 1, r, i, x);
}
t[u].val = merge(t[le(u)].val, t[ri(u)].val);
}
void update(ll i, ll x) { update(0, MINL, MAXR, i, x); }
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# [Seg Tree Kadani](seg_tree_kadani.cpp)

Implementação de uma Seg Tree que suporta update de soma e query de soma máxima em intervalo.
Implementação de uma Segment Tree que suporta update de soma em intervalo e query de soma máxima de um subarray em um intervalo. A construção é $\mathcal{O}(n)$ e as operações de consulta e update são $\mathcal{O}(log(n))$.

- Complexidade de tempo (Pré-processamento): $\mathcal{O}(N)$
- Complexidade de tempo (Consulta em intervalo): $\mathcal{O}(log(N))$
- Complexidade de tempo (Update em ponto): $\mathcal{O}(log(N))$
- Complexidade de espaço: $4 * N = \mathcal{O}(N)$
É uma Seg Tree normal, a magia está na função `merge` que é a função que combina as respostas dos filhos para computar a resposta do nodo atual. A ideia do `merge` de combinar respostas e informações já computadas dos filhos é muito útil e pode ser aplicada em muitos problemas.

**Obs**: não considera o subarray vazio como resposta.
Original file line number Diff line number Diff line change
@@ -1,62 +1,76 @@
namespace seg {
const int MAX = 1e5 + 5;
struct SegTree {
struct node {
ll pref, suff, sum, best;
ll sum, pref, suf, ans;
};
node new_node(ll v) { return node{v, v, v, v}; }
const node NEUTRAL = {0, 0, 0, 0};
node tree[4 * MAX];
node merge(node a, node b) {
ll pref = max(a.pref, a.sum + b.pref);
ll suff = max(b.suff, b.sum + a.suff);
ll sum = a.sum + b.sum;
ll best = max(a.suff + b.pref, max(a.best, b.best));
return node{pref, suff, sum, best};
const node neutral = {0, 0, 0, 0};
node merge(const node &a, const node &b) {
return {a.sum + b.sum,
max(a.pref, a.sum + b.pref),
max(b.suf, b.sum + a.suf),
max({a.ans, b.ans, a.suf + b.pref})};
}

int n;
int le(int n) { return 2 * n + 1; }
int ri(int n) { return 2 * n + 2; }
void build(int n, int esq, int dir, const vector<ll> &v) {
if (esq == dir) {
tree[n] = new_node(v[esq]);
vector<node> t;

void build(int u, int l, int r, const vector<ll> &v) {
if (l == r) {
t[u] = {v[l], v[l], v[l], v[l]};
} else {
int mid = (esq + dir) / 2;
build(le(n), esq, mid, v);
build(ri(n), mid + 1, dir, v);
tree[n] = merge(tree[le(n)], tree[ri(n)]);
int mid = (l + r) >> 1;
build(u << 1, l, mid, v);
build(u << 1 | 1, mid + 1, r, v);
t[u] = merge(t[u << 1], t[u << 1 | 1]);
}
}
void build(const vector<ll> &v) {
n = v.size();
build(0, 0, n - 1, v);

void build(int _n) { // pra construir com tamanho, mas vazia
n = _n;
t.assign(n << 2, neutral);
}
node query(int n, int esq, int dir, int l, int r) {
if (esq > r || dir < l) {
return NEUTRAL;
}
if (l <= esq && dir <= r) {
return tree[n];

void build(ll *bg, ll *en) { // pra construir com array estatico
n = int(en - bg);
t.assign(n << 2, neutral);
vector<ll> aux(n);
for (int i = 0; i < n; i++) {
aux[i] = bg[i];
}
int mid = (esq + dir) / 2;
return merge(query(le(n), esq, mid, l, r), query(ri(n), mid + 1, dir, l, r));
build(1, 0, n - 1, aux);
}

void build(const vector<ll> &v) { // pra construir com vector
n = int(v.size());
t.assign(n << 2, neutral);
build(1, 0, n - 1, v);
}
ll query(int l, int r) { return query(0, 0, n - 1, l, r).best; }
void update(int n, int esq, int dir, int x, ll v) {
if (esq > x || dir < x) {
return;

node query(int u, int l, int r, int L, int R) {
if (l > R || r < L) {
return neutral;
}
if (l >= L && r <= R) {
return t[u];
}
if (esq == dir) {
tree[n] = new_node(v);
int mid = (l + r) >> 1;
node ql = query(u << 1, l, mid, L, R);
node qr = query(u << 1 | 1, mid + 1, r, L, R);
return merge(ql, qr);
}
ll query(int l, int r) { return query(1, 0, n - 1, l, r).ans; }

void update(int u, int l, int r, int i, ll x) {
if (l == r) {
t[u] = {x, x, x, x};
} else {
int mid = (esq + dir) / 2;
if (x <= mid) {
update(le(n), esq, mid, x, v);
int mid = (l + r) >> 1;
if (i <= mid) {
update(u << 1, l, mid, i, x);
} else {
update(ri(n), mid + 1, dir, x, v);
update(u << 1 | 1, mid + 1, r, i, x);
}
tree[n] = merge(tree[le(n)], tree[ri(n)]);
t[u] = merge(t[u << 1], t[u << 1 | 1]);
}
}
void update(int x, ll v) { update(0, 0, n - 1, x, v); }
}
void update(int i, ll x) { update(1, 0, n - 1, i, x); }
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
# [Seg Tree Lazy](seg_tree_lazy.cpp)
# [Segment Tree Lazy](seg_tree_lazy.cpp)

Implementação padrão de Seg Tree com lazy update

- Complexidade de tempo (Pré-processamento): $\mathcal{O}(N)$
- Complexidade de tempo (Consulta em intervalo): $\mathcal{O}(log(N))$
- Complexidade de tempo (Update em intervalo): $\mathcal{O}(log(N))$
- Complexidade de espaço: $2 * 4 * N = \mathcal{O}(N)$
Implementação de uma Segment Tree com Lazy Propagation. Suporta operações de consulta em intervalo e update em intervalo, está implementada para responder consultas de soma e updates de soma em intervalo, ou atribuição em intervalo. A construção é $\mathcal{O}(n)$ e as operações de consulta e update são $\mathcal{O}(log(n))$.
Loading

0 comments on commit 8c000ee

Please sign in to comment.