并查集

今天又搞了一道并查集的题……果然并查集是个好东西

先简单讲一讲并查集这个东东

我们可以给每个人建立一个集合,集合的元素值有他自己,表示最开始时他不知道任何人是它的亲戚。以后每次给出一个亲戚关系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 }
黑魔法师之门

于是差不多就是这样……呵呵

posted @ 2015-11-21 11:03  呵呵的蒟蒻(≧ω≦)  阅读(150)  评论(0编辑  收藏  举报