Por Samyra Almeida
Para resolver esse problema é necessário conhecimento sobre a estrutura da segmente tree com lazy propagation e saber linearizar uma árvore usando os tempos de entrada e saída da DFS.
Vamos definir o nível de um nó o número de arestas no caminho da raiz para o nó. Raiz (nó 1) está no nível 1, os filhos da raiz estão no nível 2, os filhos dos filhos da raiz estão no nível 3 e assim por diante.
Agora, suponha que você queira fazer uma operação do tipo 1 para um nó . Quais nós da subárvore de serão adicionados (um valor positivo)? Obviamente, será o primeiro, sendo localizado no nível . Filhos de , localizado no nível , será adicionado . Filhos de filhos, localizados no nível , serão adicionados valor + val novamente. Assim, os nós da subárvore de localizados nos níveis serão adicionados a e os nós localizados nos níveis serão adicionados . Note que para um fixo, em um nível , e y um nó da subárvore de , no nível . Se e tiverem a mesma paridade, será adicionado a . Caso contrário, será adicionado a .
A partir daqui, temos a ideia de dividir os nós da árvore em dois conjuntos - os que estão localizados no nível par e os que estão localizados no nível ímpar. O que ainda torna o problema difícil de resolver? O fato de termos uma árvore. Se os nós de uma subárvore fossem uma sequência contígua em vez de alguns nós de uma árvore, o problema seria mais simples: o problema reduziria para adicionar/subtrair valores a todos os elementos de um subarray e consultar um valor atual de um elemento de matriz. Então, como podemos transformar árvore em um array, como para um nó , todos os nós da subárvore de para ser um subarray de array?
A resposta é sim. Podemos fazer isso utilizando as propriedades de busca na DFS. Antes de continuar lendo, verifique se você sabe o que é o tempo de descoberta e o horário de término em uma busca na DFS. Vamos construir 3 arrays agora - , representando nós na ordem de seus tempos de descoberta, para um nó, tempo em que foi descoberto e qual é a última vez que um nó da de sua subárvore foi descoberto antes que esse nó seja concluído. Para uma subárvore de , todos os nós na subárvore são nós em descoberta estão entre as posições de a .
Exemplo: suponha que você tenha árvore .
- é {}.
- é {}.
- é {}.
Qual é a subárvore do nó 6? elementos de descoberta da posição ao . Neste caso, de a , os elementos .
Agora, reduzimos o problema para: você recebe um vetor . você pode executar duas operações:
- aumenta todos os elementos de um intervalo para um valor ( pode ser negativo, para tratar subtrações)
- consulta o valor atual de um elemento da posição .
Podemos realizar essas operações utilizando a estrutura da segment tree com lazy propagation, se você não conhece essa estrutura, leia sobre elas antes de prosseguir.
Iremos criar duas segment tree de soma uma para os nós de nível par e outra para os de nível ímpar. Na função atualizamos um intervalo , iremos realizar a atualização nas duas segs e nas duas lazys simultanealmente, definindo para a seg ímpar e para a par, e ao consultar o valor de um nó ao chegar em uma folha da seg, basta checarmos a paridade do nível do nó e então retornamos seu o valor em sua respectiva seg.
Para maior compreensão leia atentamente o código solução abaixo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
using namespace std; | |
const int maxn=2e5+10; | |
// declaro as variáveis que vou utilizar | |
int n, m, t, num[maxn]; | |
int h[maxn], descoberta[maxn]; | |
int entrada[maxn], saida[maxn]; | |
int segi[4*maxn], segp[4*maxn]; | |
int lazyi[4*maxn], lazyp[4*maxn]; | |
vector<int> graph[maxn]; | |
void dfs(int u, int f) // linearizo a árvore | |
{ | |
descoberta[++t] = u; // adiciono u ao vetor da árvore linearizada | |
entrada[u] = saida[u] = t; // salvo os tempos de inicio e saída de u | |
h[u] = h[f] + 1; // salvo a altura d e u | |
for(auto v: graph[u]) // percorro os filhos de u | |
{ | |
if(v==f) continue; | |
dfs(v, u); | |
saida[u] = max(saida[u], saida[v]); // atualizo o tempo de saída | |
} | |
} | |
void build(int u, int l, int r) | |
{ | |
if(l == r) // se estou em uma folha da seg | |
{ | |
if(h[descoberta[l]]%2 == 1) segi[u] = num[descoberta[l]]; // se estou em um nó de nivel ímpar adiciono em segi | |
else segp[u] = num[descoberta[l]]; // se for um nó de nível par adiociono em segp | |
return; | |
} | |
int mid=(l+r)/2, e = 2*u, d = 2*u + 1; | |
build(e, l, mid); | |
build(d, mid+1, r); | |
// atualizo as duas segs | |
segi[u] = segi[e] + segi[d]; | |
segp[u] = segp[e] + segp[d]; | |
} | |
void lazyup(int u, int l, int r) // refresh na lazy | |
{ | |
int e = 2*u, d = 2*u + 1; | |
if(lazyi[u] != 0) // se a lazyi for diferente de 0 atulizado ela e propago seu valor para os filhos | |
{ | |
if(l != r) | |
{ | |
lazyi[e]+=lazyi[u]; | |
lazyi[d]+=lazyi[u]; | |
} | |
segi[u] += lazyi[u]*(r - l + 1); | |
lazyi[u] = 0; | |
} | |
if(lazyp[u] != 0) // se a lazyp for diferente de 0 atulizado ela e propago seu valor para os filhos | |
{ | |
if(l != r) | |
{ | |
lazyp[e] += lazyp[u]; | |
lazyp[d] += lazyp[u]; | |
} | |
segp[u] += lazyp[u]*(r - l + 1); | |
lazyp[u] = 0; | |
} | |
} | |
void upd(int u, int l, int r, int a, int b, int val) | |
{ | |
lazyup(u, l, r); // propago e atualizo a lazy de u | |
if(l > b || r < a) return; // se estou em um intervalo invalido, retorno | |
if(a <= l && r <= b){ // se estou em um intervalo totalmente valido | |
// atualizo as lazys | |
lazyi[u] += val; | |
lazyp[u] -= val; | |
lazyup(u, l, r); // propago e atualizo a lazy de u | |
return; | |
} | |
int mid=(l+r)/2, e = 2*u, d = 2*u + 1; | |
upd(e, l, mid, a, b, val); | |
upd(d, mid+1, r, a, b, val); | |
// atualizo as segs | |
segi[u] = segi[e] + segi[d]; | |
segp[u] = segp[e] + segp[d]; | |
} | |
int get(int u, int l, int r, int i) | |
{ | |
lazyup(u, l, r); // propago e atualizo a lazy de u | |
if(l == r) // se estou em uma folha | |
{ | |
// retorno o valor da respectiva seg | |
if(h[descoberta[l]]%2 == 1) return segi[u]; | |
else return segp[u]; | |
} | |
int mid = (l+r)/2, e = 2*u, d = 2*u + 1; | |
if(i <= mid) return get(e, l, mid, i); | |
else return get(d, mid+1, r, i); | |
} | |
int main(){ | |
scanf("%d%d", &n, &m); | |
for(int i = 1 ; i <= n ; ++i) // leio os valores dos nós | |
scanf("%d", &num[i]); | |
for(int i = 1 ; i < n ; ++i) // monto a árvore | |
{ | |
int u, v; | |
scanf("%d%d", &u, &v); | |
graph[u].push_back(v); | |
graph[v].push_back(u); | |
} | |
dfs(1, 1); // linerializo a árvore | |
build(1, 1, t); // inicio os valores nas segs | |
for(int i = 0 ; i < m ; ++i) // faço as operações | |
{ | |
int id, x; | |
scanf("%d %d", &id, &x); | |
if(id == 1){ | |
int val; | |
scanf("%d", &val); | |
if(h[x]%2 == 0) val =- val; // se eu estou em um nó do nível par, troco o sinal de val | |
upd(1, 1, t, entrada[x], saida[x], val); // atualizo as segs | |
} | |
else printf("%d\n", get(1, 1, t, entrada[x])); // imprimo o valor do nó x | |
} | |
return 0; | |
} |