题解:洛谷 AT_abc451_f Make Bipartite 3

【题目来源】

洛谷:AT_abc451_f [ABC451F] Make Bipartite 3 - 洛谷

【题目描述】

有一个无向图 \(G\),它有 \(N\) 个顶点和 \(0\) 条边,顶点的编号从 \(1\)\(N\)

给定 \(Q\) 个查询。在第 \(i\) 个查询中,向图 \(G\) 中添加一条连接顶点 \(u_i\) 和顶点 \(v_i\) 的边。

在处理完每个查询后的图 \(G\) 中,请判断是否可以将 \(G\) 的每个顶点涂成白色或黑色中的一种颜色,且满足以下条件;如果可行,则求出被涂成黑色的顶点数的最小可能值。

  • 对于每一条边,其两端的顶点都被涂上了不同的颜色。

【输入】

输入以以下形式从标准输入中给出。

\(N\) \(Q\) \(u_1\) \(v_1\) \(u_2\) \(v_2\) \(\cdots\) \(u_Q\) \(v_Q\)

【输出】

请输出 \(Q\) 行。

在第 \(i\) 行中,输出处理完第 \(i\) 个查询后的图 \(G\) 的以下数值。

  • 如果存在满足条件的涂色方式,则输出黑色顶点的最小可能数量。
  • 如果不存在满足条件的涂色方式,则输出 \(-1\)

【输入样例】

4 4
1 2
2 3
3 4
1 3

【输出样例】

1
1
2
-1

【核心思想】

  1. 问题分析:给定 \(N\) 个孤立顶点,依次添加 \(Q\) 条无向边。每次添加后立即判断图是否为二分图,若是则求黑色顶点的最小数量。这是一个动态二分图判定 + 扩展域并查集(带权并查集 / 种类并查集)问题,核心在于维护每个连通分量的染色方案,并在加边时检测奇环。

  2. 算法选择

    • 扩展域并查集:每个顶点 \(u\) 拆分为 \(u\)(代表白色)和 \(u+n\)(代表黑色)。若 \(u\)\(v\) 之间有边,则 \(u\)\(v\) 异色,即 \(u\)(白)与 \(v+n\)(黑)同集合,\(u+n\)(黑)与 \(v\)(白)同集合
    • 贪心染色:对每个连通分量,选择黑白两种颜色中数量较少的那种作为黑色顶点数,累加得到全局最小黑色数
  3. 关键步骤

    • 初始化\(p[i] = i\)\(1 \leq i \leq 2n\)),\(sz[i] = 1\)\(1 \leq i \leq n\)),\(ans = 0\)
    • 处理每条边 \((u, v)\)
      • 若之前已判定非二分图(\(ans = -1\)),直接输出 \(-1\)
      • \(find(u) = find(v+n)\)\(u\)\(v\) 已在同一集合,即同色),则形成奇环,\(ans \leftarrow -1\)
      • \(find(u) = find(v)\)(已在不同集合的异色连通块),则合并两个连通块:
        • 减去原贡献:\(ans \leftarrow ans - \min(sz[pu], sz[pun]) - \min(sz[pv], sz[pvn])\)
        • 合并:\(p[pvn] = pu\)\(sz[pu] += sz[pvn]\)\(p[pv] = pun\)\(sz[pun] += sz[pv]\)
        • 加上新贡献:\(ans \leftarrow ans + \min(sz[pu], sz[pun])\)
      • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(Q \cdot \alpha(N))\),并查集操作近似 \(O(1)\)
    • 空间复杂度:\(O(N)\),扩展域并查集数组
  5. 扩展域并查集与动态二分图的核心思想

    • 颜色对立关系建模\(u\)\(v\) 异色 \(\Leftrightarrow\) \(u\)\(v+n\) 同集合、\(u+n\)\(v\) 同集合。通过并查集的合并操作,将"异色约束"转化为"同集合约束"
    • 奇环检测:若 \(find(u) = find(v)\),说明 \(u\)\(v\) 已经是同一颜色,再添加边 \((u,v)\) 必然形成奇环,图不再是二分图
    • 连通块合并的最优染色:两个异色连通块合并后,新的连通块中每个原连通块的黑白集合被重新组合。\(sz[pu]\)\(sz[pun]\) 分别表示以 \(pu\) 为根的集合中"白域"和"黑域"的大小,\(\min\) 取较小者即为该连通块的最小黑色数
    • 增量维护答案:合并前先减去两个原连通块的贡献,合并后加上新连通块的贡献,保证 \(ans\) 始终为当前所有连通块最小黑色数之和
    • 适用于动态图二分性判定、二分图染色优化、种类并查集类问题

【算法标签】

普及+ #并查集

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 200005 * 2;  // 两倍空间
int n, q, ans;  // n: 节点数, q: 操作数, ans: 当前答案
int p[N], sz[N];  // 并查集数组和大小数组

int find(int x)  // 并查集查找,带路径压缩
{
    if (p[x] != x)  // 如果不是根
    {
        p[x] = find(p[x]);  // 路径压缩
    }
    return p[x];
}
int main()
{
    cin >> n >> q;  // 输入节点数和操作数

    // 初始化
    for (int i = 1; i <= n; i++)
    {
        sz[i] = 1;  // 初始每个集合大小为1
    }
    for (int i = 1; i <= n + n; i++)  // 初始化2n个并查集元素
    {
        p[i] = i;  // 每个元素是自己的父节点
    }

    while (q--)  // 处理每个操作
    {
        int u, v;
        cin >> u >> v;  // 输入边(u,v)

        // 如果已经违反二分图条件,或之前已违反
        if (find(u) == find(v + n) || ans == -1)
        {
            cout << ans << endl;  // 直接输出当前答案
            continue;
        }

        int pu = find(u), pv = find(v);  // 找到u和v的根
        int pun = find(u + n), pvn = find(v + n);  // 找到u'和v'的根

        // 检查u和v是否已经在同一集合(同色)
        if (pu == pv)
        {
            ans = -1;  // 违反二分图条件
            cout << ans << endl;
            continue;
        }

        // 减去原来两个连通块的贡献
        ans = ans - min(sz[pu], sz[pun]) - min(sz[pv], sz[pvn]);

        // 合并集合:u和v'合并,u'和v合并
        p[pvn] = pu;  // 将v'合并到u
        sz[pu] += sz[pvn];  // 更新大小

        p[pv] = pun;  // 将v合并到u'
        sz[pun] += sz[pv];  // 更新大小

        // 加上新连通块的贡献
        ans += min(sz[pu], sz[pun]);

        cout << ans << endl;  // 输出当前答案
    }
    return 0;
}

【运行结果】

4 4
1 2
1
2 3
1
3 4
2
1 3
-1
posted @ 2026-08-26 10:12  团爸讲算法  阅读(8)  评论(0)    收藏  举报