http://acm.hdu.edu.cn/showproblem.php?pid=2473
并查集删除点的做法:不是真正删除,而是将所有节点全部处理成非根节点,这样在做删除操作的时候,只用换一下根节点即表示删除了根节点。
如果在[0,N]中选择根节点,则不可避免的需要处理两种情况:删除非根节点;删除根节点;程序中如果直接建图添加这两种操作,则会导致TLE,因为数据量太大。
(自己做的时候用的就是直接建图添加考虑两种删除操作的思路,毫无疑问TLE)
参考网上的代码分析思路:将[0,N-1]中的所有点处理成并查集集合中的非根节点,这样在处理删除操作的时候只用换一下其对应的根节点即可,不用考虑删除该点之后会对其所处集合的影响(例如:删除非根节点怎么办?删除根节点怎么办?) 具体的处理过程为:初始化和合并的时候将[0,N-1]中的所有父节点处理成[N,2*N-1]中的节点;当需要删除节点的时候将其父节点修改为[2*N,3*N-1]中的点,保证[0,N-1] 都是集合中的非根节点。
空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M次查找的时间复杂度为O(MAlpha(N)),这里Alpha是Ackerman函数的某个反函数,在很大的范围内(人类目前观测到的宇宙范围估算有10的80次方个原子,这小于前面所说的范围)这个函数的值可以看成是不大于4的,所以并查集的操作可以看做是线性的。
数据结构:并查集
如果碰到大数据题目发现超时,则可靠的做法是尽量不要存在遍历数据的操作,尽量用开辟大数组的方法来记录结果。(自己体会用空间换时间的思想,很深奥啊)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> long f2473[1200005],N,M,top; long a2473[100005]; void makeset2473() { for(long i=0;i<N;++i) f2473[i]=i+N; for(long i=N;i<1200005;++i) f2473[i]=i; } long find2473(long x) { return f2473[x]==x?f2473[x]:f2473[x]=find2473(f2473[x]); } void union2473(long r1,long r2) { long a=find2473(r1); long b=find2473(r2); if(a==b) return ; f2473[a]=b; } int main() { freopen("in.txt","r",stdin); long k=0,A,B; char op[5];; while(1)//将scanf写到while循环中不是一个好主意,总会发生不可思议的事情 { scanf("%ld%ld",&N,&M); if(N==0&&M==0) break; top=N+N; makeset2473(); for(int i=0;i<M;++i) { scanf("%s",&op); switch(op[0]) { case 'M':{ scanf("%ld%ld",&A,&B); union2473(A,B); }break; case 'S':{ scanf("%ld",&A); f2473[A]=top++; }break; } } for(long i=0;i<N;++i) a2473[i]=find2473(i); std::sort(a2473,a2473+N); A=1; for ( long i = 1; i < N; ++ i ) if ( a2473[i] != a2473[i-1] ) A ++; printf("Case #%d: %d\n",++k,A); } return 0; }