P1197 [JSOI2008] 星球大战
星球大战题解:逆向思维与并查集应用
解题思路
这道题目要求我们处理动态删除节点后的连通块数量问题。直接处理删除操作比较困难,因此采用逆向思维:从最后的状态开始,逐步添加被删除的节点,计算连通块的变化。
关键步骤
- 
逆向处理:从所有星球都被删除的状态开始,逐步添加被删除的星球 
- 
并查集维护:使用并查集数据结构高效维护连通块 
- 
预处理:标记将被删除的星球,初始化最终状态的连通块数量 
代码注释
#include<bits/stdc++.h> #define pii pair<int,int> using namespace std; const int N = 4e5 + 10; int f[N]; // 并查集父节点数组 vector<int> g[N]; // 邻接表存储图的连接关系 pii t[N]; // 存储所有边的数组 int vis[N]; // 标记星球是否被摧毁 (1表示被摧毁) int bad[N]; // 存储将被摧毁的星球顺序 int ans[N]; // 存储每个阶段的答案 int n, m, q; // n-星球数, m-边数, q-摧毁次数 // 并查集查找函数(带路径压缩) int find(int x) { if(f[x] != x) f[x] = find(f[x]); return f[x]; } // 并查集合并函数 void merge(int x, int y) { int fx = find(x), fy = find(y); f[fy] = fx; // 将fy的父节点设为fx } int main() { cin >> n >> m; // 初始化并查集 for(int i = 0; i < n; i++) f[i] = i; // 读入图的边关系并构建邻接表 for(int i = 1; i <= m; i++) { int x, y; cin >> x >> y; g[x].push_back(y); g[y].push_back(x); t[i] = {x, y}; // 存储边 } // 读入将被摧毁的星球 cin >> q; for(int i = 1; i <= q; i++) { cin >> bad[i]; vis[bad[i]] = 1; // 标记为将被摧毁 } // 初始状态:所有未被摧毁的星球单独成块 ans[q + 1] = n - q; // 预处理:构建最终状态(所有被标记星球已摧毁) for(int i = 1; i <= m; i++) { int x = t[i].first, y = t[i].second; // 如果两个星球都未被摧毁,且不在同一连通块 if(!vis[x] && !vis[y] && find(x) != find(y)) { merge(x, y); // 合并 ans[q + 1]--; // 连通块数量减少 } } // 逆向处理:逐个添加被摧毁的星球 for(int i = q; i >= 1; i--) { int x = bad[i]; // 当前要恢复的星球 vis[x] = 0; // 标记为未摧毁 ans[i] = ans[i + 1] + 1; // 初始假设新增一个连通块 // 遍历该星球的所有邻居 for(int y : g[x]) { // 如果邻居未被摧毁且不在同一连通块 if(!vis[y] && find(x) != find(y)) { merge(x, y); // 合并 ans[i]--; // 连通块数量减少 } } } // 输出结果:从初始状态到最终状态 for(int i = 1; i <= q + 1; i++) cout << ans[i] << endl; return 0; }
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号