Solução por Leonardo Paes
Conhecimento prévio necessário:
Para resolvermos esse problema, utilizaremos uma DFS para percorrer a árvore e precalcular o tamanho de cada subárvore, considerando que a árvore está enraizada no vértice de número 1.
Tendo o tamanho de cada subárvore calculado, faremos o seguinte: Suponha que estamos no vértice , com cor . Iremos considerar nesse passo apenas os caminhos que passam através de sem passar por vértices de cor previamente calculados na ordem da , ou seja, iremos aumentar apenas caminhos de cor . Temos dois tipos de caminhos a considerar: caminhos que começam em alguma subárvore de , passam por e terminam em outra subárvore de . E caminhos que começam em alguma subárvore de e terminam em algum vértice que não está na subárvore de .
Seja a quantidade de filhos de e seja o tamanho da subárvore do i-ésimo filho. A quantidade de caminhos do primeiro tipo é igual ao seguinte somatório:
Para calcularmos isso em um tempo menor que , basta observarmos que: será multiplicado com , será multiplicado com , e assim por diante. Portanto, sendo , basta utilizarmos o seguinte algoritmo, em pseudo-código:
Para cada filho de , retiramos seu tamanho, de e incrementamos a resposta de em .
Para calcularmos os caminhos do segundo tipo, devemos tomar muito cuidado, para evitar recontar caminhos diversas vezes, pois devemos contá-los apenas uma vez.
Seja a quantidade de vértices tal que, partindo de um vértice de cor , eu chego nesses vértices após passar por algum outro vértice de cor , sem ser , que foram calculados antes na ordem da DFS. Devemos evitar contar caminhos que terminam nesses vértices, pois eles já foram contados previamente. Portanto, temos dois casos a considerar para calcular o valor de : ao chamar algum filho de para ser calculado e ao terminar de calcular a resposta para toda a subárvore de .
Antes de tudo, criaremos uma variável _ que guardará o inicial.
No primeiro caso, ao chamarmos algum filho para ser calculado, devemos considerar que toda a árvore, exceto a subárvore desse filho, já foi calculada. Portanto, .
No segundo caso, após terminarmos de calcular a subárvore de , temos que _, pois, para todos os vértices que posteriormente serão calculados que tem a cor , para chegar em algum filho de , será necessário passar por .
Após termos calculado o corretamente, ao calcularmos a resposta de um vértice , incrementamos a resposta da cor em , pois contaremos os caminhos começando em alguém da subárvore de e terminando em alguém fora dela, sem passar novamente em algum vértice de cor .
Confira o código abaixo para melhor entendimento:
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; | |
typedef long long ll; | |
int n, c[maxn], sz[maxn], used[maxn]; | |
ll ans[maxn]; | |
vector<int> grafo[maxn]; | |
void dfs(int u, int p = 0){ | |
sz[u] = 1; | |
for(auto v : grafo[u]){ | |
if(v == p) continue; | |
dfs(v, u); | |
sz[u] += sz[v]; | |
} | |
} | |
void solve(int u, int p = 0){ | |
ans[c[u]] += 1LL*(n - sz[u] - used[c[u]] + 1) * sz[u]; | |
ll sum = sz[u] - 1; | |
for(auto v : grafo[u]){ | |
if(v == p) continue; | |
sum -= sz[v]; | |
ans[c[u]] += sz[v] * sum; | |
} | |
int previous_used = used[c[u]]; | |
for(auto v : grafo[u]){ | |
if(v == p) continue; | |
used[c[u]] = n - sz[v]; | |
solve(v, u); | |
} | |
used[c[u]] = previous_used + sz[u]; | |
} | |
int main(){ | |
ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL); | |
cin >> n; | |
for(int i=1; i<=n; i++) cin >> c[i]; | |
for(int i=1; i<n; i++){ | |
int u, v; | |
cin >> u >> v; | |
grafo[u].push_back(v); | |
grafo[v].push_back(u); | |
} | |
dfs(1); | |
solve(1); | |
for(int i=1; i<=n; i++) cout << ans[i] << "\n"; | |
return 0; | |
} |