Как через DSU посчитать число компонент связности после каждого добавленного ребра?
n вершин, изначально 0 рёбер. Рёбра приходят по одному, после каждого надо вывести текущее число компонент связности. Хочется онлайн и быстро. Как держать счётчик корректно, учитывая кратные рёбра (которые не меняют связность)?
2 ответа
Старт: comps = n (каждая вершина — своя компонента). Каждое успешное объединение (концы были в разных компонентах) уменьшает счётчик на 1; кратное ребро внутри одной компоненты ничего не меняет.
DSU dsu(n); // comps инициализирован как n внутри DSU
for (auto& [u, v] : edges) {
dsu.unite(u, v); // внутри: если слил разные — --comps
cout << dsu.comps << '\n'; // число компонент после этого ребра
}
Где unite ровно такой, как в DSU с размерами: возвращает false и не трогает comps, если find(u)==find(v) (кратное ребро/петля). Это автоматически корректно обрабатывает кратные рёбра — они просто не уменьшают счётчик.
Сложность — O(α(n)) на ребро, итого O((n+m)·α(n)), память O(n). Онлайн, ничего предсчитывать не нужно. Если попросят ещё и размер наибольшей компоненты после каждого ребра — держите maxSize и обновляйте его в unite после слияния (maxSize = max(maxSize, sz[корня])).
Обратная задача (рёбра удаляются, число компонент растёт) онлайн так не решается — DSU не умеет разъединять. Её сводят к этой через обращение времени (обрабатываем удаления как добавления с конца, см. отдельный вопрос про реверс времени). И аккуратно с самопетлями u==v: они не должны уменьшать comps; в unite это уже покрыто проверкой find(u)==find(v), но если вы пишете счётчик вручную снаружи, легко забыть и получить заниженное число компонент.