LeetCode HOT100 - 除法求值

比较直观的想法就是建图,遇到查询就 dfs 跑一下图

因为题目保证了答案不冲突,所以直接跑就可以

考虑 dfs 实际上是自底向上考虑,也就是父节点得到子节点的什么信息,结合自身节点的信息来得到答案

class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& a, vector<double>& b, vector<vector<string>>& q) {
        unordered_map<string, vector<pair<string, double>>> adj;
        unordered_set<string> st;
        for (int i = 0; i < a.size(); i++) {
            string u = a[i][0], v = a[i][1];
            adj[u].emplace_back(v, b[i]);
            adj[v].emplace_back(u, 1.0 / b[i]);
            st.insert(u);
            st.insert(v);
        }
        vector<double> ans;
        auto dfs = [&](auto self, string x, string y, unordered_set<string>& vis) -> double {
            if (x == y) return 1.0;
            vis.insert(x);
            for (auto [nxt, val] : adj[x]) {
                if (vis.count(nxt)) continue;
                double res = self(self, nxt, y, vis);
                if (res != -1.0) {
                    return res * val;
                }
            }
            return -1.0;
        };
        for (auto i : q) {
            string x = i[0], y = i[1];
            if (st.find(x) == st.end() || st.find(y) == st.end()) {
                ans.emplace_back(-1.0);
                continue;
            }
            unordered_set<string> vis;
            double res = dfs(dfs, x, y, vis);
            ans.emplace_back(res);
        }
        return ans;
    }
};

正解是带权并查集

如果这题没有要我们求具体的解,仅问能否根据我们已有的信息去达到询问的答案

那么显然就是使用并查集

但现在我们想要答案,也就是我们需要有权重,比如从子节点到根节点的权重,因此,也就是带权并查集

实际上也不限于权值,带权并查集是在在普通并查集“只维护谁和谁连通”的基础上,再额外维护“节点到父节点(或到根)的相对关系”。

这个关系常见的有

  • 距离差:dist[y] - dist[x] = w
  • 奇偶关系:color[x] xor color[y] = 0/1
  • 模 3 关系:食物链那种 type[y] - type[x] ≡ w (mod 3)

d[x] 表示 x 到根节点的势能差,即 val[x] - val[f[x]]

find 的时候,路径压缩要顺便更新 d[x]

merge 的时候要根据题目的关系,推导根和根之间应该满足什么关系

#include <bits/stdc++.h>
using namespace std;

struct DSU {
    std::vector<int> f, siz;
    std::vector<double> d;
    // d[x] = val[x] / val[root(x)](在 find 之后成立)
    // 更准确地说,平时 d[x] 是 val[x] / val[f[x]]
    // 路径压缩后会更新成 val[x] / val[root]

    DSU() {}
    DSU(int n) {
        init(n);
    }

    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
        d.assign(n, 1.0); // 乘法单位元是 1
    }

    int find(int x) {
        if (x == f[x]) {
            return x;
        }
        int p = f[x];
        f[x] = find(f[x]);
        d[x] *= d[p];
        return f[x];
    }

    bool same(int x, int y) {
        return find(x) == find(y);
    }

    // 返回 val[x] / val[y]
    double ratio(int x, int y) {
        find(x);
        find(y);
        return d[x] / d[y];
    }

    // 添加关系:val[x] / val[y] = w
    bool merge(int x, int y, double w) {
        int rx = find(x);
        int ry = find(y);

        if (rx == ry) {
            return std::fabs(ratio(x, y) - w) < 1e-9;
        }

        // 按大小合并
        if (siz[rx] < siz[ry]) {
            std::swap(rx, ry);
            std::swap(x, y);
            w = 1.0 / w;
        }

        // 现在把 ry 挂到 rx 下
        //
        // 已知:
        // val[x] / val[y] = w
        // d[x] = val[x] / val[rx]
        // d[y] = val[y] / val[ry]
        //
        // 设要维护:
        // d[ry] = val[ry] / val[rx]
        //
        // 则:
        // val[x] = d[x] * val[rx]
        // val[y] = d[y] * val[ry]
        //
        // 所以:
        // (d[x] * val[rx]) / (d[y] * val[ry]) = w
        // => val[ry] / val[rx] = d[x] / (w * d[y])
        //
        // 即:
        // d[ry] = d[x] / (w * d[y])

        siz[rx] += siz[ry];
        f[ry] = rx;
        d[ry] = d[x] / (w * d[y]);
        return true;
    }

    int size(int x) {
        return siz[find(x)];
    }
};

class Solution {
public:
    vector<double> calcEquation(vector<vector<string>>& a, vector<double>& b, vector<vector<string>>& q) {
        vector<string> c;
        for (auto &i : a) {
            c.emplace_back(i[0]);
            c.emplace_back(i[1]);
        }
        sort(c.begin(), c.end());
        c.erase(unique(c.begin(), c.end()), c.end());
        auto get = [&](const string &s) -> int {
            return lower_bound(c.begin(), c.end(), s) - c.begin();
        };
        auto exist = [&](const string &s) -> bool {
            return binary_search(c.begin(), c.end(), s);
        };
        DSU dsu(c.size());
        for (int i = 0; i < a.size(); i++) {
            int x = get(a[i][0]);
            int y = get(a[i][1]);
            dsu.merge(x, y, b[i]);
        }
        vector<double> ans;
        for (auto i : q) {
            string x = i[0], y = i[1];
            if (!exist(x) || !exist(y)) {
                ans.push_back(-1.0);
                continue;
            }
            int u = get(x);
            int v = get(y);
            if (!dsu.same(u, v)) {
                ans.push_back(-1.0);
            } else {
                ans.push_back(dsu.ratio(u, v));
            }
        }
        return ans;
    }
};
posted @ 2026-03-29 00:49  rdcamelot  阅读(18)  评论(0)    收藏  举报