带权并查集

带权并查集

用途

维护联通块中元素的相对关系

如:

有长度为 \(n\) 的序列,\(A_1,A_2,A_3,\ldots,A_n\)

  • 1 a b w:加入一个约束

    \(A_a-A_b=w\)

  • 2 a b:询问

    \(A_a-A_b\)

不仅如此,还能判断给定的约束是否冲突,询问可不可求

讲解

定义

只有能获取以下信息的运算,才能被带权并查集维护

抽象关系为:

struct Node {
    
};

抽象合并为:

Node op(Node a, Node b)

抽象逆元为:

Node inv(Node a)

抽象自指为:

Node self(int i)

关系

\[\Large i \xrightarrow{Node} j \]

表示并查集中点 \(i\) 与 点 \(j\) 的关系,可以为 \(\oplus\)\(-\) 等等

比如 \(A_i-A_j=Node\)\(A_i\oplus A_j=Node\) \(\dots\)

合并

\[\Large i \xrightarrow{Node_1} j \xrightarrow{Node_2} k \iff i \xrightarrow{op(Node_1, \ Node2)} k \]

表示该如何合并两段关系,以减法举例:

\[\Large i \xrightarrow{-1} j \xrightarrow{-3} k \iff i \xrightarrow{-4} k \]

故对减法,我们令:

Node op(Node a, Node b) {
    return Node(a + b);
}

同理,对于 \(\oplus\) :

Node op(Node a, Node b) {
    return Node(a ^ b);
}

逆元

\[\Large i \xrightarrow{Node} j \iff j \xrightarrow{inv(Node)} i \]

表示一段关系的逆向,以减法举例:

\[\Large i \xrightarrow{-3} j \iff j \xrightarrow{+3} i \]

故对减法,我们令:

Node inv(Node a) {
    return -a;
}

同理:对于 \(\oplus\) :

Node inv(Node a) {
    return a;
}

自指

\[\Large i \xrightarrow{self(i)} i \]

这是描述自己与自己的关系的量。

通常我们设为被维护运算的幺元

故对减法和 \(\oplus\),我们都令:

Node self(int i) {
    return 0;
}

实现过程

这是一个普通的并查集:

struct DSU {
    vector<int> fa;
    DSU(int x) {
        fa.resize(x);
        iota(fa.begin(), fa.end(), 0);
    }
    int find(int x) {
        if (fa[x] == x)
            return x;
        return fa[x] = find(fa[x]);
    }
    bool check(int x, int y) {
        int fx = find(x), fy = find(y);
        return fx == fy;
    }
    void merge(int x, int y) {
        int fx = find(x);
        int fy = find(y);
        if (fx == fy)
            return;
        fa[fy] = fx;
    }
};

把每个部分依次修改为带权并查集

外部定义

vector<int> fa;
vector<Node> val;

我们需要一个数组维护关系,其中 \(val_i\) 表示 \(i\)\(fa_i\) 的关系,即:

\[\Large i\xrightarrow{val_i}fa_i \]

构造函数

DSU(int x) {
    fa.resize(x);
    iota(fa.begin(), fa.end(), 0);
    val.resize(x);
    for(int i = 0;i < x;i++) {
        val[i] = self(i);
    }
}

我们需要额外初始化维护的权值为它自己和自己的关系。

find

int find(int x) {
    if (fa[x] == x)
        return x;
    int f = fa[x];
    fa[x] = find(f);
    val[x] = op(val[x], val[f]);
    return fa[x];
}

此步需要推导:

现在的关系:

\[\Large x \xrightarrow{val_x} fa[x] \xrightarrow{val_{fa[x]}} fa[fa[x]] \]

我们进行路径压缩

\[\Large x \xrightarrow{op(val_x,\ val_{fa[x]})} fa[fa[x]] \]

注意:我们必须把它上面的路径合并完,才能合并它和根,故先 \(find\) 后赋值 \(val\),每次我们都认为递归返回后 \(fa[x]\) 已经是根

check

这个不变。

query

pair<bool, Node> query(int a, int b) {
    int ra = find(a);
    int rb = find(b);
    if (ra != rb)
        return {false, self(1)};
    Node res = op(val[a], inv(val[b]));
    return {true, res};
}

这是我们需要新增的函数,用于查询 \(a\)\(b\) 两点的相对关系。

若它们不属于一个联通块,说明现在的信息不足以求解。

否则:

由于已经执行过 \(find\) 的路径压缩,若两点属于一个联通块,它们的深度必然为 \(2\)

所以一定有 \(fa[a] = fa[b] = root\) ,故我们知道:

\[\Large a \xrightarrow{val_a} root \xleftarrow{val_b} b \iff a \xrightarrow{val_a} root \xrightarrow{inv(val_b)} b \iff a \xrightarrow{op(val_a,\ inv(val_b))} b \]

使用 \(op\) 合并即可求解。

merge

bool merge(int a, int b, Node d) {
    int ra = find(a);
    int rb = find(b);
    if (ra == rb) {
        return query(a, b).second == d;
    }
    fa[ra] = rb;
    val[ra] = op(op(inv(val[a]), d), val[b]);
    return true;
}

添加约束:

\[\Large a\xrightarrow{d}b \]

如果信息已经足以解出 \(a \xrightarrow{d} b\) 中的 \(d\) ,那就要解出来,用于判断这次 \(merge\) 是废话,还是产生了冲突。

否则,我们合并 \(a\)\(b\) 两个块的信息,执行完 \(fa[ra] = rb\) 后,现在的情况是:

\[\Large a \xrightarrow{val_a} ra \xrightarrow{x} rb \xleftarrow{val_b} b \]

我们不知道 \(x\) 是什么,需要求解它。

我们先进行转化:

\[\Large ra \xrightarrow{inv(val_a)} a \xrightarrow{d} b \xrightarrow{val_b} rb \]

故:

\[\Large ra \xrightarrow{op(op(inv(val_a),d),val_b)} rb \]

合并即可。

STD

struct V_DSU {
    struct Node {
        int x;
        Node(int x = 0) { this->x = x; }
        bool operator==(const Node &other) const { return x == other.x; }
    };
    Node self(int i) {
        Node res;
        // SELF
        return res;
    }
    Node op(Node a, Node b) {
        Node res;
        // OP
        return res;
    }
    Node inv(Node a) {
        Node res;
        // INV
        return res;
    }
    vector<int> fa;
    vector<Node> val;
    V_DSU(int x) {
        fa.resize(x);
        iota(fa.begin(), fa.end(), 0);
        val.resize(x);
        for (int i = 0; i < x; i++) {
            val[i] = self(i);
        }
    }
    int find(int x) {
        if (fa[x] == x)
            return x;
        int f = fa[x];
        fa[x] = find(f);
        val[x] = op(val[x], val[f]);
        return fa[x];
    }
    pair<bool, Node> query(int a, int b) {
        int ra = find(a);
        int rb = find(b);
        if (ra != rb)
            return {false, self(a)};
        Node res = op(val[a], inv(val[b]));
        return {true, res};
    }
    bool merge(int a, int b, Node d) {
        int ra = find(a);
        int rb = find(b);
        if (ra == rb) {
            return query(a, b).second == d;
        }
        fa[ra] = rb;
        val[ra] = op(op(inv(val[a]), d), val[b]);
        return true;
    }
};
posted @ 2026-08-13 21:31  曼波绿豆哈基蜂  阅读(0)  评论(0)    收藏  举报