1.题目
http://acm.hdu.edu.cn/showproblem.php?pid=2473
2.分析
并查集删除点的做法:不是真正删除,而是将所有节点全部处理成非根节点,这样在做删除操作的时候,只用换一下根节点即表示删除了根节点。
如果在[0,N]中选择根节点,则不可避免的需要处理两种情况:删除非根节点;删除根节点;程序中如果直接建图添加这两种操作,则会导致TLE,因为数据量太大。
(自己做的时候用的就是直接建图添加考虑两种删除操作的思路,毫无疑问TLE)
参考网上的代码分析思路:将[0,N-1]中的所有点处理成并查集集合中的非根节点,这样在处理删除操作的时候只用换一下其对应的根节点即可,不用考虑删除该点之后会对其所处集合的影响(例如:删除非根节点怎么办?删除根节点怎么办?) 具体的处理过程为:初始化和合并的时候将[0,N-1]中的所有父节点处理成[N,2*N-1]中的节点;当需要删除节点的时候将其父节点修改为[2*N,3*N-1]中的点,保证[0,N-1] 都是集合中的非根节点。
3.复杂度
空间复杂度为O(N),建立一个集合的时间复杂度为O(1),N次合并M次查找的时间复杂度为O(MAlpha(N)),这里Alpha是Ackerman函数的某个反函数,在很大的范围内(人类目前观测到的宇宙范围估算有10的80次方个原子,这小于前面所说的范围)这个函数的值可以看成是不大于4的,所以并查集的操作可以看做是线性的。
4.涉及内容
数据结构:并查集
5.感想
如果碰到大数据题目发现超时,则可靠的做法是尽量不要存在遍历数据的操作,尽量用开辟大数组的方法来记录结果。(自己体会用空间换时间的思想,很深奥啊)
6.代码
#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;
}
7.参考文献
这样当删除节点x之后,x节点对应的新的虚拟节点存在数组B中,当后面有处理和x相关的合并操作的时候,可以直接在B中查到新的节点来操作。
这样能够正确处理程序的原因是:每次在执行union操作的时候参数用的都是B数组中的数据(b包含新增加节点的数据),从而保证能正确删除