P2835 刻录光盘 floyd/并查集思想
解题思路与代码注释
解题思路
这道题目需要解决的是有向图的传递闭包问题,要求找出最少需要多少个起点(刻录光盘的营员),使得通过这些起点可以到达图中的所有节点(所有营员都能获得资料)。
关键步骤:
-
构建邻接矩阵:记录每个营员愿意直接拷贝给哪些其他营员
-
计算传递闭包:使用Floyd-Warshall算法找出所有间接可达关系
-
合并可达节点:将有直接或间接拷贝关系的营员合并到同一集合
-
统计根节点数量:每个独立的根节点代表需要一个光盘
代码注释
#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; }

浙公网安备 33010602011771号