05 Divisao Conquista Arvores
05 Divisao Conquista Arvores
05 Divisao Conquista Arvores
UFPR
06/07/2022
1
Busca Binária
2
Esse tal de lg parece bom!
3
A busca binária
Em Algoritmos I devem te contar a historinha de como você
acharia uma palavra no dicionário. Como o dicionário está
ordenado, um jeito bem eficiente é abrir o meio, verificar se o que
você está procurando está antes ou depois, e ir dividindo o que
resta ao meio até encontrar a palavra que você quer. Isso é
eficiente, pois é logarítmico, e lg é uma operação eficiente:
n lg n n lg n n lg n
100 0,000 1010 33,219 1020 66,438
101 3,321 1011 36,541 1021 69,760
102 6,643 1012 39,863 1022 73,082
103 9,965 1013 43,185 1023 76,404
104 13,287 1014 46,506 1024 79,726
105 16,609 1015 49,828 1025 83,048
106 19,931 1016 53,150 1026 86,370
107 23,253 1017 56,472 1027 89,692
108 26,575 1018 59,794 1028 93,013
109 29,897 1019 63,116 1029 96,335
4
O que precisa pra fazer busca binária?
5
Note o espaço!
6
Mas no que eu posso aplicar, então?
7
Achando posição depois do valor em vetor ordenado
Código upperboundmanual.cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; while (cin >> n) {
vector<int> v (n); for (int& x : v) { cin >> x; }
int lo = 0, hi = n;
while (lo < hi) {
int mi = lo + (hi-lo) / 2; // evite overflow!
if (v[mi] <= 2) { lo = mi + 1; }
else { hi = mi; }
} cout << lo << "\n";
}
}
Entrada Saída
7 1 2 3 4 5 6 7 2
7 1 2 2 2 3 4 5 4
7 1 1 1 2 2 3 4 5
8
Achando posição depois do valor em vetor ordenado (STL)
Código upperbound.cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; while (cin >> n) {
vector<int> v (n); for (int& x : v) { cin >> x; }
auto it = upper_bound(v.begin(), v.end(), 2);
cout << it - v.begin() << "\n";
}
}
Entrada Saída
7 1 2 3 4 5 6 7 2
7 1 2 2 2 3 4 5 4
7 1 1 1 2 2 3 4 5
9
Operações prontas na STL
10
lower_bound nas estruturas da STL
11
Quero ler mais sobre!
12
Implementação alternativa: Passo binário
Código binarystep.cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; while (cin >> n) {
vector<int> v (n); for (int& x : v) { cin >> x; }
int l = n, r = n;
for (int p = n; p >= 1; p /= 2) {
while (l-p >= 0 && !(v[l-p] < 2)) { l -= p; }
while (r-p >= 0 && !(v[r-p] <= 2)) { r -= p; }
}
cout << r - l << "\n";
}
}
Entrada Saída
7 1 2 3 4 5 6 7 1
7 1 2 2 2 3 4 5 3
7 1 1 1 2 2 3 4 2
13
Divisão e Conquista
14
Divisão e Conquista
15
Contando inversões
Inversões... que?
• Em ciência da computação e em matemática discreta, uma
sequência tem uma inversão quando dois de seus elementos
estão fora de sua ordem natural.
• Seja A uma sequência. Se i < j e A[i] > A[j], então o par de
índices (i, j) ou o par de elementos (A[i], A[j]) é chamado de
inversão de A.
Notação: Iv0 é um conjunto de inversão de par de índices indexados
em 0 do vetor v . Iv é o conjunto de inversão de elementos de v .
h i
v1 = 0 41221334
Iv01 = {(0, 1), (0, 2), (0, 3), (1, 2)}
Iv1 = {(4, 2), (4, 1), (4, 3), (2, 1)}
16
Exemplo de inversão
h i
v1 = 0 11325374
Iv1 = {}
17
Exemplo de inversão
h i
v1 = 0 11325374
Iv1 = {}
17
Exemplo de inversão
h i
v2 = 0 11327354
Iv2 = {(2, 3)}
17
Exemplo de inversão
h i
v2 = 0 11327354
Iv2 = {(2, 3)}
17
Exemplo de inversão
h i
v3 = 0 11723354
Iv3 = {(1, 2), (1, 3)}
17
Exemplo de inversão
h i
v3 = 0 11723354
Iv3 = {(1, 2), (1, 3)}
17
Exemplo de inversão
h i
v4 = 0 71123354
Iv4 = {(0, 1), (0, 2), (0, 3)}
17
Exemplo de inversão
h i
v4 = 0 71123354
Iv4 = {(0, 1), (0, 2), (0, 3)}
17
Exemplo de inversão
h i
v5 = 0 71125334
Iv5 = {(0, 1), (0, 2), (0, 3), (2, 3)}
17
Exemplo de inversão
h i
v5 = 0 71125334
Iv5 = {(0, 1), (0, 2), (0, 3), (2, 3)}
17
Exemplo de inversão
h i
v6 = 0 71521334
Iv6 = {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)}
17
Exemplo de inversão
h i
v6 = 0 71521334
Iv6 = {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)}
17
Exemplo de inversão
h i
v7 = 0 71 52 33 14
Iv7 = {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}
17
Exemplo de inversão
h i
v7 = 0 71 52 33 14
Iv7 = {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}
|Iv7 | = n(n − 1)/2 = (4 · 3)/2 = 6
17
Merge Sort
39 27 43 3 9 82 10
39 27 43 3 9 82 10
39 27 43 3 9 82 10
39 27 43 3 9 82 10
27 39 3 43 9 82 10
3 27 39 43 9 10 82
3 9 10 27 39 43 82
18
Merge Sort
Código mergesort.h
int swaps = 0;
void merge_sort(int l, int r) {
if (r - l == 1) { return; }
int mi = l + (r - l) / 2;
merge_sort(l, mi); merge_sort(mi, r);
vector<int> aux (r - l);
int i = l, j = mi;
for (int k = 0; k < r - l; k++) {
if (i < mi && j < r) {
if (!(a[i] < a[j])) { swaps += mi - i; }
if (a[i] < a[j]) { aux[k] = a[i++]; }
else { aux[k] = a[j++]; }
} else if (i < mi) { aux[k] = a[i++]; }
else { aux[k] = a[j++]; }
}
copy(aux.begin(), aux.end(), a.begin()+l);
}
19
Fusão
i j
h i
v1 = 0 213253144566
swaps = 0
20
Fusão
i j0 j
h i
v1 = 0 213253144566
swaps = 0
20
Fusão
i j0 j
h i
v2 = 0 213213544566
swaps = 1
20
Fusão
0
i j j
h i
v3 = 0 211233544566
swaps = 2
20
Fusão
j0 i j
h i
v4 = 0 112233544566
swaps = 3
20
Fusão
i j
h i
v4 = 0 112233544566
swaps = 3
20
Fusão
i j
h i
v4 = 0 112233544566
swaps = 3
20
Fusão
i j
h i
v4 = 0 112233544566
swaps = 3
20
Fusão
0
i j j
h i
v4 = 0 112233544566
swaps = 3
20
Fusão
j0 i j
h i
v5 = 0 112233445566
swaps = 4
20
Implementação de contar inversões
Código mergesort.cpp
#include <bits/stdc++.h>
using namespace std;
vector<int> a (1e5+15);
#include "mergesort.h"
int main() {
int n; while (cin >> n) {
for (int i = 0; i < n; i++)
cin >> a[i];
swaps = 0;
merge_sort(0, n);
cout << swaps << "\n";
}
}
Entrada Saída
4 4 2 1 3 4
4 7 5 3 1 6
6 2 3 5 1 4 6 4
21
Quero saber mais sobre inversões!
22
Árvores
23
Somando intervalos
Pr
Dado um vetor v e os limites [l, r ], devolva a soma i=l vi
• O(n) para calcular a soma uma vez
• Com consultas, fica O(qn)
• Soma acumulada (aula passada) permite consultas O(1) —
conseguimos O(q)!
E se fosse permitido alterar os valores no meio do problema?
24
Somando intervalos e alterando valores
25
Árvore de Fenwick (BIT)
26
Responsabilidades de cada posição da árvore
2 6
1 3 5 7
27
Responsabilidades de cada posição da árvore (em binário)
1000
0100
0010 0110
28
O caminho do bit menos significativo
Para cada índice 1 ≤ i ≤ n, vamos guardar a soma dos últimos
LSOne(i) elementos (incluindo i), onde LSOne(x ) devolve o valor
do bit menos significativo de x .
Observações importantes:
• Caminho do decremento: Para qualquer valor de i,
podemos decrementá-lo em LSOne(i) e eventualmente
chegaremos em i = 0: 111 → 110 → 100 → 000.
• Caminho do incremento: Para qualquer valor de i > 0,
podemos incrementá-lo em LSOne(i) e eventualmente
teremos i ≥ n: 001 → 010 → 100 → . . . .
• Seja x = LSOne(i). Os x − 1 valores imediatamente antes de
i têm i em seus caminhos do incremento. Para i = 6, temos
0110 e 0101.
• O caminho do decremento de i passa por um valor no
caminho do incremento de cada valor j ≤ i.
Essa última fica mais fácil com um desenho.
29
Caminhando com LSOne(i)
1000
0100
0010 0110
30
Associação de elementos na BIT
31
Implementação da BIT
Código bit.h
vector<int> bit (N, 0);
void add(int i, int delta) {
for (; i < N; i += i & (-i))
bit[i] += delta;
}
int get(int i) {
int ans = 0;
for (; i > 0; i -= i & (-i))
ans += bit[i];
return ans;
}
32
Usando a Árvore de Fenwick
33
Atualização em posição e consulta em intervalo
Código updatepos.cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+15;
#include "bit.h"
int main() {
char op; int a, b, i, d; string s;
while (cin >> op) {
if (op == ’+’) {
cin >> i >> d; add(i, d);
} else if (op == ’q’) {
cin >> a >> b;
cout << get(b) - get(a-1) << "\n";
} else getline(cin, s);
}
}
34
Atualização em intervalo e consulta em posição (cont.)
Entrada Saída
# 0 0 0 0 0 3
+ 1 +1 6
# 1 0 0 0 0 2
+ 2 +2 -1
# 1 2 0 0 0 2
+ 5 +3 -3
# 1 2 0 0 3
q 1 3
q 1 5
+ 2 -1
# 1 1 0 0 3
+ 3 -3
# 1 1 -3 0 3
q 1 2
q 1 3
q 1 5
q 3 3
35
Atualização em intervalo e consulta em posição
Código updateinterval.cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+15;
#include "bit.h"
int main() {
char op; int a, b, d, i; string s;
while (cin >> op) {
if (op == ’+’) {
cin >> a >> b >> d;
add(a, d); add(b+1, -d);
} else if (op == ’q’) {
cin >> i; cout << get(i) << "\n";
} else getline(cin, s);
}
}
36
Atualização em intervalo e consulta em posição (cont.)
Entrada Saída
# 0 0 0 0 0 2
+ 1 5 +2 0
# 2 2 2 2 2 5
+ 4 5 +3 7
# 2 2 2 5 5 2
+ 1 2 -2
# 0 0 2 5 5
q 3
q 2
q 4
+ 1 4 +2
# 2 2 4 7 5
q 4
q 2
37
Árvore de Segmentos
38
Árvore de Segmentos para um vetor de 5 elementos
Para o vetor V = [1, 3, −2, 8, −7]:
[1, 5]
3
[1, 3] [4, 5]
2 1
[1, 1] [2, 2]
1 3
39
Consulta
Qual a soma do intervalo [3, 5]?
[1, 5]
3
[1, 3] [4, 5]
2 1
[1, 1] [2, 2]
1 3
40
Atualização
[1, 5]
8
[1, 3] [4, 5]
7 1
[1, 1] [2, 2]
1 3
41
Complexidade da Árvore de Segmentos
• Construção: O(N)
• Consulta: O(log N)
• Atualização: O(log N)
42
Notas sobre a implementação recursiva
43
Consultas e alterações na árvore de segmentos recursiva
Código strec.h
vector<int> t (4*N);
int op_inclusive(int l, int r, int ti=1, int tl=1, int tr=N) {
if (l > r) { return NEUTRAL; }
if (l == tl && r == tr) { return t[ti]; }
int tm = (tl + tr) / 2;
return OP(op_inclusive(l, min(r, tm), ti*2, tl, tm),
op_inclusive(max(l, tm+1), r, ti*2+1, tm+1, tr));
}
void set_value(int i, int v, int ti=1, int tl=1, int tr=N) {
if (tl == tr) { t[ti] = v; return; }
int tm = (tl + tr) / 2;
if (i <= tm)
set_value(i, v, ti*2, tl, tm);
else
set_value(i, v, ti*2+1, tm+1, tr);
t[ti] = OP(t[ti*2], t[ti*2+1]);
}
44
Construção da árvore de segmentos recursiva
Código strecbuild.h
void build(vector<int>& src, int ti=1, int tl=1, int tr=N) {
if (tl == tr) {
if (tl < src.size()) { t[ti] = src[tl]; }
return;
}
int tm = (tl + tr) / 2;
build(src, ti*2, tl, tm);
build(src, ti*2+1, tm+1, tr);
t[ti] = OP(t[ti*2], t[ti*2+1]);
}
45
Notas sobre a versão iterativa
46
Consultas e alterações na árvore de segmentos iterativa
Código stit.h
vector<int> t (2*N);
Código stitbuild.h
void build(vector<int>& src) {
for (int i = 1; i < src.size(); i++)
t[N+i] = src[i];
for (int i = N - 1; i > 0; i--)
t[i] = OP(t[2*i], t[2*i+1]);
}
48
Usando a árvore de segmentos para somas
Código sumseg.cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+15;
#define NEUTRAL 0
#define OP(X, Y) (X + Y)
#include "stit.h"
#include "stitbuild.h"
int main() {
int n; cin >> n; vector<int> src (n+1);
for (int i = 1; i <= n; i++) { cin >> src[i]; }
build(src);
char op; int a, b, v, i; string s;
while (cin >> op)
if (op == ’=’) {
cin >> i >> v; set_value(i, v);
} else if (op == ’q’) {
cin >> a >> b;
cout << op_inclusive(a, b) << "\n";
} else getline(cin, s);
} 49
Usando a árvore de segmentos para somas (continuado)
Entrada Saída
5 1 2 0 0 0 3
= 5 3 6
# 1 2 0 0 3 2
q 1 3 -1
q 1 5 2
= 2 1 -3
# 1 1 0 0 3
= 3 -3
# 1 1 -3 0 3
q 1 2
q 1 3
q 1 5
q 3 3
50
Usando a árvore de segmentos para máximo
Código maxseg.cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+15, oo = 987654321;
#define NEUTRAL -oo
#define OP(X, Y) max(X, Y)
#include "strec.h"
#include "strecbuild.h"
int main() {
int n; cin >> n; vector<int> src (n+1);
for (int i = 1; i <= n; i++) { cin >> src[i]; }
build(src);
char op; int a, b, v, i; string s;
while (cin >> op)
if (op == ’=’) {
cin >> i >> v; set_value(i, v);
} else if (op == ’q’) {
cin >> a >> b;
cout << op_inclusive(a, b) << "\n";
} else getline(cin, s);
} 51
Usando a árvore de segmentos para máximo (continuado)
Entrada Saída
5 1 2 0 0 0 2
= 5 3 3
# 1 2 0 0 3 1
q 1 3 1
q 1 5 3
= 2 1 -3
# 1 1 0 0 3
= 3 -3
# 1 1 -3 0 3
q 1 2
q 1 3
q 1 5
q 3 3
52
E isso é tudo!
53