题解:洛谷 P10378 交流问题

【题目来源】

洛谷:P10378 [GESP202403 七级] 交流问题 - 洛谷

【题目描述】

来自两所学校 \(A\)\(B\)\(n\) 名同学聚在一起相互交流。为了方便起见,我们把这些同学从 \(1\)\(n\) 编号。他们共进行了 \(m\) 次交流,第 \(i\) 次交流中,编号为 \(u_i, v_i\) 的同学相互探讨了他们感兴趣的话题,并结交成为了新的朋友。

由于这次交流会的目的是促进两校友谊,因此只有不同学校的同学之间会交流。同校同学并不会互相交流。

作为 \(A\) 校顾问,你对 \(B\) 校的规模非常感兴趣,你希望求出 \(B\) 校至少有几名同学、至多有几名同学。

【输入】

第一行两个正整数,表示同学的人数 \(n\)、交流的次数 \(m\)
接下来 \(m\) 行,每行两个整数 \(u_i, v_i\),表示一次交流。

【输出】

输出一行两个整数,用单个空格隔开,分别表示 \(B\) 校至少有几名同学、至多有几名同学。

【输入样例】

4 3
1 2
2 3
4 2

【输出样例】

1 3

【核心思想】

  1. 问题分析:给定 \(n\) 名同学和 \(m\) 次跨校交流记录,交流只发生在不同学校的同学之间。需要确定 \(B\) 校的最小和最大可能人数。这是一个二分图染色 + 连通分量分析问题,核心在于将交流关系建模为二分图,每个连通分量有两种染色方案,分别对应两所学校。

  2. 算法选择

    • 二分图染色(DFS/BFS):将图按交流关系进行二分图染色,验证图是否为二分图(题目保证是)
    • 连通分量统计:对每个连通分量统计两种颜色的节点数,分别累加最小值和最大值
  3. 关键步骤

    • 读入数据:读取 \(n, m\) 和交流记录,构建无向图
    • 遍历所有节点\(i\)\(1\)\(n\)):
      • \(col[i] = 0\)(未染色):发现新连通分量
        • \(col[i] \leftarrow 1\)\(num[1] \leftarrow 1\)\(num[2] \leftarrow 0\)
        • \(dfs(i)\):递归染色整个连通分量
        • \(ans1 \leftarrow ans1 + \min(num[1], num[2])\)\(B\) 校最少人数)
        • \(ans2 \leftarrow ans2 + \max(num[1], num[2])\)\(B\) 校最多人数)
    • 输出结果\(ans1\)\(ans2\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m)\),DFS 遍历所有节点和边
    • 空间复杂度:\(O(n + m)\),邻接表和颜色数组
  5. 二分图染色与连通分量的核心思想

    • 二分图建模:交流只发生在不同学校之间,因此交流关系图天然是二分图,两种颜色分别代表 \(A\) 校和 \(B\)
    • 连通分量的独立性:不同连通分量之间没有交流约束,因此可以独立选择哪所学校对应哪种颜色
    • 最优分配策略:对每个连通分量,若颜色 \(1\)\(num[1]\) 人、颜色 \(2\)\(num[2]\) 人:
      • \(B\) 校最少:每个分量选择较小的颜色作为 \(B\) 校,\(\sum \min(num[1], num[2])\)
      • \(B\) 校最多:每个分量选择较大的颜色作为 \(B\) 校,\(\sum \max(num[1], num[2])\)
    • 染色传递\(col[v] = 3 - col[u]\) 实现颜色翻转(\(1 \leftrightarrow 2\)),确保相邻节点颜色不同
    • 适用于二分图判定、连通分量独立决策、最大最小化分配类问题

【算法标签】

普及 #二分图

【代码详解】

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

const int N = 1e5 + 5;  // 最大节点数
int n, m, s, t;         // n: 节点数, m: 边数, s,t: 边的两个端点
int ans1, ans2;         // ans1: 最小染色数, ans2: 最大染色数
int num[3];             // num[1]: 颜色1的节点数, num[2]: 颜色2的节点数
int col[N];             // 每个节点的颜色,0表示未染色
vector<int> e[N];       // 邻接表存储图

// 深度优先搜索进行二分图染色
void dfs(int u)
{
    // 遍历节点u的所有邻居节点v
    for (auto v : e[u])
    {
        // 如果邻居节点v还未染色
        if (col[v] == 0)
        {
            // 将v染成与u不同的颜色
            // 如果col[u]=1,则col[v]=2;如果col[u]=2,则col[v]=1
            col[v] = 3 - col[u];
            num[col[v]]++;  // 对应颜色的节点数加1
            dfs(v);         // 递归染色v的邻居
        }
    }
}

int main()
{
    // 输入节点数和边数
    cin >> n >> m;

    // 输入边,构建无向图
    for (int i = 1; i <= m; i++)
    {
        cin >> s >> t;
        e[s].push_back(t);
        e[t].push_back(s);
    }

    // 遍历所有节点
    for (int i = 1; i <= n; i++)
    {
        // 如果节点i还未染色,说明找到一个新的连通分量
        if (col[i] == 0)
        {
            // 初始化当前连通分量的颜色计数
            num[1] = 1;  // 从颜色1开始,所以num[1]=1
            num[2] = 0;  // 颜色2初始为0

            // 将节点i染成颜色1
            col[i] = 1;

            // 从节点i开始进行DFS染色
            dfs(i);

            // 统计当前连通分量的结果
            // ans1: 最小化颜色1和颜色2的较小值
            // ans2: 最大化颜色1和颜色2的较大值
            ans1 += min(num[1], num[2]);
            ans2 += max(num[1], num[2]);
        }
    }

    // 输出结果
    cout << ans1 << " " << ans2 << endl;

    return 0;
}

【运行结果】

4 3
1 2
2 3
4 2
1 3
posted @ 2026-07-17 15:52  团爸讲算法  阅读(3)  评论(0)    收藏  举报