并查集
今天又搞了一道并查集的题……果然并查集是个好东西
先简单讲一讲并查集这个东东
我们可以给每个人建立一个集合,集合的元素值有他自己,表示最开始时他不知道任何人是它的亲戚。以后每次给出一个亲戚关系a, b,则a和他的亲戚与b和他的亲戚就互为亲戚了,将a所在集合与b所在集合合并。——以上为好搜上的解释
不过这的确是重点
并查集用一个数组来存储它的父亲,实际上并查集就相当于很多棵树组成的森林。
1 //---------------------------------------------------------------------------- 2 int fa[200005]; 3 void csh(int x){for (i=0;i<x;i++) {fa[i]=i;h[i]=0;}} 4 int find(int x) 5 { 6 if (fa[x]==x) return x; 7 else return find(fa[x]); 8 } 9 void wjh(int x,int y) 10 { 11 x=find(x);y=find(y); 12 if (x==y) return;fa[x]=y; 13 } 14 bool same(int x,int y){return find(x)==find(y);} 15 //----------------------------------------------------------------------------
不过这么做效率并不是很高……所以有两个优化:第一个优化是启发式合并。在优化单链表时,我们将较短的表链到较长的表尾,在这里我们可以用同样的方法,将深度较小的树指到深度较大的树的根上。这样可以防止树的退化,最坏情况不会出现。
1 //---------------------------------------------------------------------------- 2 int fa[200005],h[200005]; 3 void csh(int x){for (i=0;i<x;i++) {fa[i]=i;h[i]=0;}} 4 int find(int x) 5 { 6 if (fa[x]==x) return x; 7 else return find(fa[x]); 8 } 9 void wjh(int x,int y) 10 { 11 x=find(x);y=find(y); 12 if (x==y) return; 13 if (h[x]<h[y]) fa[x]=y; 14 else {fa[y]=x;if (h[x]==h[y]) h[x]++;} 15 } 16 bool same(int x,int y){return find(x)==find(y);} 17 //----------------------------------------------------------------------------
现在时间复杂度就是O(log n)了。不过这还可能不够快(会被卡),于是就有了第二个优化:路径压缩。它非常简单而有效。我们在找祖先是"顺便"将经过所有节点的父节点全改为祖先,以后再调用时就只需O(1)的时间。
1 //---------------------------------------------------------------------------- 2 int fa[200005],h[200005]; 3 void csh(int x){for (i=0;i<x;i++) {fa[i]=i;h[i]=0;}} 4 int find(int x) 5 { 6 if (fa[x]==x) return x; 7 else return fa[x]=find(fa[x]); 8 } 9 void wjh(int x,int y) 10 { 11 x=find(x);y=find(y); 12 if (x==y) return; 13 if (h[x]<h[y]) fa[x]=y; 14 else {fa[y]=x;if (h[x]==h[y]) h[x]++;} 15 } 16 bool same(int x,int y){return find(x)==find(y);} 17 //----------------------------------------------------------------------------
于是时间复杂度变成了O(a(n)),这个a(n)是阿克曼函数的反函数,这个不是重点,只有知道这在有意义的n下小于4,可以看成函数。
—————————————————————————————————————————————————————————————————————————
现在是我做的一道题:黑魔法师之门。
这道题题目描述差点没看懂……后来才明白是什么情况。
其实就是找环
如果在一个环内多连接一条边就多了一种选择,所以答案乘以二。最后输出答案减一。
代码如下:
1 #include<iostream> 2 #define M 1000000009 3 using namespace std; 4 //---------------------------------------------------------------------------- 5 int n,m,i,x,y,ans,fa[200005],h[200005]; 6 void csh(int x){for (i=0;i<x;i++) {fa[i]=i;h[i]=0;}} 7 int find(int x) 8 { 9 if (fa[x]==x) return x; 10 else return fa[x]=find(fa[x]); 11 } 12 void wjh(int x,int y) 13 { 14 x=find(x);y=find(y); 15 if (x==y) return; 16 if (h[x]<h[y]) fa[x]=y; 17 else {fa[y]=x;if (h[x]==h[y]) h[x]++;} 18 } 19 bool same(int x,int y){return find(x)==find(y);} 20 //---------------------------------------------------------------------------- 21 int main() 22 { 23 scanf("%d%d",&n,&m);csh(n);ans=1; 24 for (i=0;i<m;i++) 25 { 26 scanf("%d%d",&x,&y); 27 x=find(x);y=find(y); 28 if (x!=y) wjh(x,y); 29 else ans*=2,ans%=M; 30 printf("%d\n",(ans-1+M)%M); 31 } 32 //system("pause"); 33 return 0; 34 }
于是差不多就是这样……呵呵