Skip to content

CF891C Envy

原题链接:CF891C Envy

Tag:MST

题目描述

对于一个连通的无向带权图 \(G\),MST(最小生成树)是 \(G\) 的一个子图, 包含 \(G\) 的所有顶点,是一棵树,并且其所有边的权值之和尽可能小。

给定一个图 \(G\)。如果你在该图上运行最小生成树算法, 只会得到一棵最小生成树,这就让其它边“嫉妒”了。 你会得到若干个询问,每个询问包含 \(G\) 的一个边集合, 你需要判断是否存在一棵包含这些边的最小生成树。

数据说明:

\(2 \leq n,m \leq 5 \cdot 10^5\)\(n-1 \leq m\)

\(u_i \neq v_i\)\(1 \leq w_i \leq 5 \cdot 10^5\)

\(1 \leq q \leq 5 \cdot 10^5\)\(1 \leq k_i \leq n-1\)

保证所有询问的 \(k_i\) 之和不超过 \(5 \cdot 10^5\)

分析

首先回顾一下 \(\text{Kruskal}\) 的过程:

  1. 按照权值从小到大扫描边
  2. 如果一个边的两个点处在不同的连通块中就选,反之跳过。

我们可以从「按权值分层」的角度来重新审视 \(\text{Kruskal}\)

当我们处理到某个权值 \(w\) 时,所有权值 \(< w\) 的边一定已经产生了若干连通块,而对于边权 \(=w\) 的边, 我们就要从这些边里挑选一些出来,只要这些边不成环,无论怎么选都不会影响到最优性。

也就是说,当我们考虑到权值 \(w\) 时,整个图已经被边权 \(<w\) 的边缩成了若干块, 而我们在这些块之间挑选权值为 \(w\) 的边时,只要这些边没有形成环,就能出现在某个 \(\text{MST}\) 中。

思考出以上内容还是比较平凡的,之后我们发现 \(n,m,q\) 最大都有 \(5e5\) 的值,而且 \(\sum k\) 最大也有 \(5e5\), 所以如果我们要对每一个询问都跑一次 \(\text{Kruskal}\) 一定是不对的。

我们可以先把原图中的边权排序,之后把询问记录成 \((w,qid,eid)\) 的形式, 其中 \(w\) 代表边的权值,之后以 \(w\) 作为第一关键字,\(qid\) 作为第二关键字进行排序。

维护一个全局的 \(\text{DSU}\),用它来记录已经加入了所有边权 \(<w\) 的状态。

之后我们从小到大考虑所有出现的 \(w\),首先对于 \(qid\) 相同的这些边, 我们要判断其是否出现自环,或者这些边连到一起之后是否会出现环,这一步可以开一个临时的 \(\text{DSU}\) 实现。 一旦出现了环的状况,答案自然是 \(\text{NO}\)

之后把所有边权 \(=w\) 的边,使用全局的 \(\text{DSU Union}\) 到一起, 这样一来复杂度为 \(O((m+\sum k) \log \sum k)\)

代码实现

struct DSU {
public:
    vector<int> bcj;
    vector<int> siz;
    void Init(int n) {
        bcj.resize(n + 1);
        siz.resize(n + 1);
        for (int i = 1; i <= n; ++i) {
            bcj[i] = i;
            siz[i] = 1;
        }
    }
    int Find(int x) {
        if (bcj[x] == x) return x;
        return bcj[x] = Find(bcj[x]);
    }
    // 按秩合并
    void Union(int x, int y) {
        x = Find(x);
        y = Find(y);
        if (x == y) return;
        if (siz[x] > siz[y]) swap(x, y);
        bcj[x] = y;
        siz[y] += siz[x];
    }
};

void IHaveNoLimitation() {
    int n, m;
    cin >> n >> m;
    struct edge {
        int u, v, w;
    };
    vector<edge> E(m + 1);
    vector<int> W;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        E[i] = {u, v, w};
        W.push_back(w);
    }
    int q;
    cin >> q;
    vector<int> ans(q, 1);
    struct query {
        int w, qid, eid;
    };
    vector<query> querys;
    for (int i = 0; i < q; i++) {
        int k;
        cin >> k;
        while (k--) {
            int id;
            cin >> id;
            querys.push_back({E[id].w, i, id});
            W.push_back(E[id].w);
        }
    }
    std::sort(W.begin(), W.end());
    W.erase(std::unique(W.begin(), W.end()), W.end());
    vector<int> Eidx(m);
    std::iota(Eidx.begin(), Eidx.end(), 1);
    std::sort(Eidx.begin(), Eidx.end(), [&](int a, int b) {
        return E[a].w < E[b].w;
    });
    std::sort(querys.begin(), querys.end(), [&](query a, query b) {
        if (a.w != b.w) return a.w < b.w;
        return a.qid < b.qid;
    });
    DSU dsu;
    dsu.Init(n);
    int p1 = 0, p2 = 0;
    for (auto &w: W) {
        int rL = p2;
        while (p2 < querys.size() && querys[p2].w == w) p2++;
        for (int i = rL; i < p2;) {
            int qid = querys[i].qid;
            int j = i;
            while (j < p2 && querys[j].qid == qid) j++;
            if (ans[qid]) {
                vector<int> tmp;
                vector<PII> pairs;
                bool f = 0;
                for (int t = i; t < j; t++) {
                    int id = querys[t].eid;
                    int a = dsu.Find(E[id].u);
                    int b = dsu.Find(E[id].v);
                    if (a == b) {
                        f = 1;
                        break;
                    }
                    pairs.push_back({a, b});
                    tmp.push_back(a),tmp.push_back(b);
                }
                if (!f) {
                    std::sort(tmp.begin(), tmp.end());
                    tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
                    vector<int> p(tmp.size()), sz(tmp.size(), 1);
                    std::iota(p.begin(), p.end(), 0);
                    auto Find = [&](int x) {
                        while (p[x] != x) {
                            p[x] = p[p[x]];
                            x = p[x];
                        }
                        return x;
                    };
                    auto Union = [&](int x, int y) {
                        x = Find(x);
                        y = Find(y);
                        if (x == y) return false;
                        if (sz[x] < sz[y]) swap(x, y);
                        p[y] = x;
                        sz[x] += sz[y];
                        return true;
                    };
                    for (auto &[a, b]: pairs) {
                        int ia = lower_bound(tmp.begin(), tmp.end(), a) - tmp.begin();
                        int ib = lower_bound(tmp.begin(), tmp.end(), b) - tmp.begin();
                        if (!Union(ia, ib)) {
                            f = 1;
                            break;
                        }
                    }
                }
                if (f) ans[qid] = 0;
            }
            i = j;
        }
        while (p1 < Eidx.size() && E[Eidx[p1]].w == w) {
            int id = Eidx[p1];
            dsu.Union(E[id].u, E[id].v);
            p1++;
        }
    }
    for (auto &x: ans) {
        if (x) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
}

日志

本页面创建于 2026/3/1 16:38