← Все вопросы

Как поддержать DSU с весами/чётностью рёбер (взвешенный union-find), чтобы отвечать «X и Y одной группы или разных»?

Задан 10 месяцев назад636 просмотров2 ответа
8

Задача типа «фуд-чейн / двудольность по запросам»: даны утверждения «A и B в одной группе» или «A и B в разных группах», нужно ловить противоречия. Понимаю, что это DSU, но как хранить отношение (чётность пути до корня) и корректно его пересчитывать при сжатии путей?

2 ответа

12
✓ Принятый ответ — помог автору

Это DSU с потенциалами: помимо родителя храним rel[x] — отношение x к своему родителю (для двудольности это бит чётности: 0 — та же доля, 1 — другая). При сжатии путей пересчитываем rel как сумму (xor для чётности) вдоль пути.

struct DSUParity {
    vector<int> p, rel; // rel[x] = чётность пути x -> p[x] (0/1)
    DSUParity(int n): p(n), rel(n,0){ iota(p.begin(),p.end(),0); }
    pair<int,int> find(int x){ // {корень, чётность x->корень}
        if (x==p[x]) return {x,0};
        auto [r, pr] = find(p[x]);
        p[x]=r; rel[x]^=pr; // сжатие + накопление чётности
        return {r, rel[x]};
    }
    // d=0: A,B одной доли; d=1: разных. false => противоречие
    bool unite(int a, int b, int d){
        auto [ra, da] = find(a);
        auto [rb, db] = find(b);
        if (ra==rb) return (da ^ db) == d;
        p[ra]=rb; rel[ra] = da ^ db ^ d;
        return true;
    }
};

Идея: чётность между A и B = rel(A->корень) xor rel(B->корень). Если корни совпали — просто проверяем, что наблюдаемая чётность совпадает с заявленной d; если нет — противоречие. Если корни разные — подвешиваем и подбираем rel корня так, чтобы соотношение выполнилось. Сложность O(α(n)) амортизированно на запрос, память O(n). Для k>2 групп (как в «фуд-чейн») rel хранят по модулю k и используют сложение/вычитание вместо xor.

5

Самая частая бага — рекурсивный find, который сначала сжимает, а потом читает rel[x] из устаревшего состояния. В коде выше порядок важен: сперва рекурсивно идём к корню, получаем pr (чётность родителя до корня после сжатия), и только потом rel[x]^=pr. Если переставить — получите неверные чётности на втором запросе. Итеративную версию писать заметно сложнее (нужен второй проход), так что здесь рекурсия оправдана, но следите за глубиной стека на 10^6.

Ваш ответ

Войдите, чтобы ответить на вопрос.