「CodeChef GERALD07」Chef and Graph Queries

题目链接:CodeChef GERALD07

大厨有一个无向图 $G$。顶点从 $1$ 到 $n$ 标号,边从 $1$ 到 $m$ 标号。

大厨有 $q$ 对询问 $L_i, R_i$。对于每对询问,大厨想知道当仅保留编号 $X$ 满足 $L_i \le X \le R_i$ 所在的边时,图 $G$ 中有多少了连通块。

注意数据可能包含自环和重边!

本题有 $T$ 组数据。

数据范围:$1 \le T \le 10 ^ 3$,$1\le n, m, q \le 2\times 10 ^ 5$,$1\le L_i \le R_i \le M$,所有的 $n, m, q$ 的和均不超过 $2\times 10 ^ 5$。


Solution

首先我们把连通块个数转化一下:对于每个连通块维护生成树,假如所有生成树的总边数为 $x$,那么答案为 $n - x$。

离线

我们将询问按照 $R_i$ 为关键字从小到大排序。按顺序加入 $1$ 到 $m$ 的边,对于边 $(u, v)$ 考虑如下 $2$ 种情况:

  • 点 $u, v$ 不连通:直接在加入这条边。
  • 点 $u, v$ 已连通:删除路径 $u, v$ 上编号最小的边,再加入这条边。这个更新方法和「Codeforces 594D」REQ题解)很相似。

最后考虑如何计算答案。很显然我们可以用 $\text{LCT}$ 维护这些生成树;边是否在生成树内可以用权值线段树维护。

时间复杂度:$\mathcal O((m + q)\log n)$。

在线

考虑一下强制在线的做法。

我们直接按照 $1$ 到 $m$ 的顺序加边,两种情况和上述离线做法一样讨论。而现在的问题是如何统计答案。

考虑一条边 $i$ 有贡献,也是 $2$ 种情况:

  • 可以替换掉 $[1, i - 1]$ 中的一条边.
  • 在加入之前的 $i - 1$ 条边后得到的生成树中,这条边的两个顶点不连通。

那么问题转化为:查询 $L$ 到 $R$ 中有多少条边可以替换掉 $1$ 到 $L - 1$ 内的边,再加上第二种情况的贡献。

我们可以用主席树统计答案。建立 $m$ 棵权值线段树,第 $i$ 棵线段树内的第 $j$ 个位置维护第 $1$ 到 $j$ 条边可以被多少条 $j + 1$ 到 $i$ 的边替换掉。为了方便查询,第二种情况的贡献统一加到线段树的第 $0$ 个位置。

对于每次询问 $L, R$,我们对第 $R$ 和 $L - 1$ 棵线段树的 $0$ 到 $L - 1$ 位置作差,再用 $n$ 减去这个值就是答案。

时间复杂度:$\mathcal O((m + q) \log n)$。


Code

篇幅限制,本文只给出在线解法的代码。

#include <cstdio>
#include <algorithm>
#include <cassert>

const int N = 2e5 + 5;
const int INF = 0x7f7f7f7f;

int n, m, q, E[N][2], f[N];

struct Node;
Node *null;

struct Node {
    Node *ch[2], *fa;
    int val, mn;
    bool rev;
    Node() {
        ch[0] = ch[1] = fa = null;
        val = mn = rev = 0;
    }
    bool get() {
        return fa->ch[1] == this;
    }
    bool isroot() {
        return fa->ch[0] != this && fa->ch[1] != this;
    }
    void reverse() {
        rev ^= 1, std::swap(ch[0], ch[1]);
    }
    void pushup() {
        mn = std::min(val, std::min(ch[0]->mn, ch[1]->mn));
    }
    void pushdown() {
        if (rev) {
            ch[0]->reverse();
            ch[1]->reverse();
            rev = 0;
        }
    }
} *a[N << 1];

struct LCT {
    void build(int n) {
        null = new Node();
        null->ch[0] = null->ch[1] = null->fa = null;
        null->val = null->mn = INF;
        for (int i = 1; i <= n; i++) {
            a[i] = new Node();
        }
    }
    void clear(int n) {
        delete null;
        for (int i = 1; i <= n; i++) {
            delete a[i];
        }
    }
    void pushtag(Node *x) {
        if (!x->isroot()) pushtag(x->fa);
        x->pushdown();
    }
    void rotate(Node *x) {
        Node *y = x->fa, *z = y->fa;
        int k = x->get();
        !y->isroot() && (z->ch[y->get()] = x), x->fa = z;
        y->ch[k] = x->ch[!k], x->ch[!k]->fa = y;
        x->ch[!k] = y, y->fa = x;
        y->pushup();
    }
    void splay(Node *x) {
        pushtag(x);
        while (!x->isroot()) {
            Node *y = x->fa;
            if (!y->isroot()) {
                rotate(x->get() == y->get() ? y : x);
            }
            rotate(x);
        }
        x->pushup();
    }
    void access(Node *x) {
        for (Node *y = null; x != null; x = (y = x)->fa) {
            splay(x), x->ch[1] = y, x->pushup();
        }
    }
    void makeroot(Node *x) {
        access(x), splay(x), x->reverse();
    }
    void split(Node *x, Node *y) {
        makeroot(x), access(y), splay(y);
    }
    void link(Node *x, Node *y) {
        split(x, y);
        x->fa = y;
    }
    void cut(Node *x, Node *y) {
        split(x, y);
        x->fa = y->ch[0] = null, y->pushup();
    }
};

struct Chairman {
    static const int M = 30 * N;
    int idx, rt[N], ls[M], rs[M], sum[M];
    void clear() {
        idx = 0;
    }
    void build(int &p, int l, int r) {
        p = ++idx, ls[p] = rs[p] = sum[p] = 0;
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(ls[p], l, mid);
        build(rs[p], mid + 1, r);
    }
    void modify(int &p, int l, int r, int u, int x, int v) {
        p = ++idx, ls[p] = ls[u], rs[p] = rs[u], sum[p] = sum[u] + v;
        if (l == r) return;
        int mid = (l + r) >> 1;
        if (x <= mid) {
            modify(ls[p], l, mid, ls[u], x, v);
        } else {
            modify(rs[p], mid + 1, r, rs[p], x, v);
        }
    }
    int query(int p, int l, int r, int x, int y) {
        if (x <= l && r <= y) return sum[p];
        int mid = (l + r) >> 1;
        if (y <= mid) {
            return query(ls[p], l, mid, x, y);
        } else if (x > mid) {
            return query(rs[p], mid + 1, r, x, y);
        } else {
            return query(ls[p], l, mid, x, mid) + query(rs[p], mid + 1, r, mid + 1, y);
        }
    }
} C;

int find(int x) {
    return f[x] == x ? x : f[x] = find(f[x]);
}
int main() {
    int T;
    for (scanf("%d", &T); T--; ) {
        scanf("%d%d%d", &n, &m, &q);
        LCT T;
        T.build(n + m);
        C.build(C.rt[0], 0, m);
        for (int i = 1; i <= n; i++) f[i] = i;
        for (int i = 1; i <= n + m; i++) {
            a[i]->val = (i > n ? i - n : INF);
        }
        for (int i = 1; i <= m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            E[i][0] = u, E[i][1] = v;
            if (u == v) {
                C.rt[i] = C.rt[i - 1];
                continue;
            }
            if (find(u) == find(v)) {
                T.split(a[u], a[v]);
                int j = a[v]->mn;
                T.cut(a[E[j][0]], a[n + j]);
                T.cut(a[E[j][1]], a[n + j]);
                T.link(a[u], a[n + i]);
                T.link(a[v], a[n + i]);
                C.modify(C.rt[i], 0, m, C.rt[i - 1], j, 1);
            } else {
                f[find(u)] = find(v);
                T.link(a[u], a[n + i]);
                T.link(a[v], a[n + i]);
                C.modify(C.rt[i], 0, m, C.rt[i - 1], 0, 1);
            }
        }
        for (int i = 1; i <= q; i++) {
            int l, r;
            scanf("%d%d", &l, &r);
            int ans = C.query(C.rt[r], 0, m, 0, l - 1) - C.query(C.rt[l - 1], 0, m, 0, l - 1);
            printf("%d\n", n - ans);
        }
        T.clear(n + m);
        C.clear();
    }
    return 0;
}

发表评论