P2835 刻录光盘 floyd/并查集思想

解题思路与代码注释

解题思路

这道题目需要解决的是有向图的传递闭包问题,要求找出最少需要多少个起点(刻录光盘的营员),使得通过这些起点可以到达图中的所有节点(所有营员都能获得资料)。

关键步骤:

  1. 构建邻接矩阵:记录每个营员愿意直接拷贝给哪些其他营员

  2. 计算传递闭包:使用Floyd-Warshall算法找出所有间接可达关系

  3. 合并可达节点:将有直接或间接拷贝关系的营员合并到同一集合

  4. 统计根节点数量:每个独立的根节点代表需要一个光盘

代码注释

#include<bits/stdc++.h>
using namespace std;
const int N = 2e2 + 10;  // 定义常量N为210,足够容纳最大200个营员

int f[N], g[N][N];  // f数组用于并查集,g是邻接矩阵
int n;  // 营员数量

int main() {
    cin >> n;  // 输入营员数量
    
    // 初始化并查集,每个营员初始时独立
    for(int i = 1; i <= n; i++) f[i] = i;
    
    // 构建初始邻接矩阵
    for(int i = 1; i <= n; i++) {
        int x; 
        // 读取第i个营员愿意拷贝给的营员列表,以0结束
        while(cin >> x, x) {
            g[i][x] = 1;  // 标记i可以直接拷贝给x
        }
    }
    
    // Floyd-Warshall算法计算传递闭包
    for(int k = 1; k <= n; k++) {
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                // 如果i可以通过k到达j,则更新i到j的可达性
                g[i][j] = g[i][j] || (g[i][k] && g[k][j]);
            }
        }
    }
    
    // 合并有拷贝关系的营员
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            if(g[i][j]) {  // 如果i可以直接或间接拷贝给j
                f[j] = f[i];  // 将j合并到i所在的集合
            }
        }
    }
    
    // 统计需要的光盘数量(独立集合的数量)
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        if(f[i] == i) {  // 如果是根节点
            ans++;  // 需要一张光盘
        }
    }
    
    cout << ans;  // 输出结果
    return 0;
}

 

posted @ 2025-05-27 20:42  CRt0729  阅读(42)  评论(0)    收藏  举报