Solução: Inheritance (JOI 2015)

Solução: Inheritance

Primeiro, vamos dar a cada filho sua própria estrutura de DSU para armazenar quais cidades estão conectadas com suas ferrovias, e, em seguida, iterar pelas ferrovias em ordem decrescente de preço, dando cada ferrovia ao primeiro filho que puder aceitá-la. Isso é efetivamente o mesmo que percorrer cada filho, um de cada vez, e construir uma árvore geradora máxima para cada um deles individualmente.

No entanto, iterar linearmente por cada filho resultaria numa complexidade de \mathcal{O}(KM), o que não passa nos limites. Para encontrar rapidamente o primeiro filho que pode aceitar uma ferrovia, vamos usar busca binária. Mas para ver que isso realmente funciona primeiro precisamos provar o seguinte: Independentemente da ferrovia que estivermos processando, todos os filhos após o primeiro filho que podem aceitar a ferrovia atual também podem aceitar essa ferrovia, enquanto todas as crianças antes dessa criança não podem.

Para que isso se mantenha, as componentes conectadas de cada DSU devem ser um subconjunto das posteriores. Isso é obviamente verdadeiro para quando começamos, portanto, agora tudo o que temos de provar é que dar uma ferrovia ao primeiro filho que pode aceitá-la preservará essa relação. Digamos que tenhamos dois filhos adjacentes em termos de idade: c_i e c_{i+1}, sendo c_i o mais velho e c_{i+1} o primeiro filho que pode aceitar a ferrovia atual.

Se dar a c_{i+1} a ferrovia fizesse com que fosse impossível obter c_i do novo c_{i+1}, isso significaria que a ferrovia conectaria alguns dois componentes de c_i e, portanto, também significaria que c_i deveria ser o primeiro a aceitar a ferrovia, e não c_{i+1}. O uso da busca binária reduz a complexidade de \mathcal{O}(KM) para \mathcal{O}(K \log M), que passa no limite de tempo.

Veja o código a seguir para tirar eventuais dúvidas:


#include <bits/stdc++.h>
using namespace std;
const int MAXK = 2e4 + 10;
const int MAXM = 3e5 + 10;
struct DSU
{
vector<int> pai, sze;
void init(int n)
{
pai.resize(n + 1);
sze.resize(n + 1);
iota(pai.begin(), pai.end(), 0);
fill(sze.begin(), sze.end(), 1);
}
int find(int u)
{
return pai[u] = (pai[u] == u ? u : find(pai[u]));
}
void join(int u, int v)
{
u = find(u), v = find(v);
if(u == v) return;
if(sze[u] < sze[v]) swap(u, v);
pai[v] = u; sze[u] += sze[v];
}
bool same(int u, int v)
{
return (find(u) == find(v));
}
} children[MAXK];
int n, m, k, resp[MAXM];
vector<tuple<int, int, int, int>> edges; // peso, id, v1, v2
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> m >> k;
for(int i = 1; i <= m; i++){
int u, v, w; cin >> u >> v >> w;
edges.emplace_back(w, i, u, v);
}
// Ordeno em ordem decrescente de pesos
sort(edges.begin(), edges.end(), greater<tuple<int, int, int, int>>());
for(int i = 1; i <= k; i++)
children[i].init(n);
for(auto [w, id, u, v] : edges){
int l = 1, r = k, opt = 0;
// Busca binaria
while(l <= r){
int mid = (l + r) >> 1;
if(!children[mid].same(u, v)) opt = mid, r = mid - 1;
else l = mid + 1;
}
if(opt) children[opt].join(u, v);
resp[id] = opt;
}
for(int i = 1; i <= m; i++)
cout << resp[i] << '\n';
}

view raw

inheritance.cpp

hosted with ❤ by GitHub