每日算法——并查集的应用

Posted on 2014-04-14 21:13  SymenYang  阅读(1844)  评论(1编辑  收藏  举报

  并查集是一种群众喜闻乐见的数据结构,其复杂度是数据结构中最奇葩的之一了,Tarjan证明其为阿克曼函数的反函数,在可以想象(不全面的解释啊)的范围内小于等于3。。。我们就把它当做O(1)吧。下面通过几道基础的并查集来探查一下并查集的应用。递归调用并查集

  最裸的并查集就只有表示一个集合的功能,支持动态的合并,查询两者是否属于一个集合,这部分内容太简单,请自行Baidu。

  在并查集上可以加入边权,成为加权并查集,一般来说这类题目形式比较单一,纯属个人娱乐吧。。

  加权并查集裸题——银河英雄传说

  这是一道最裸的加权并查集,对于每一个节点维护一个到最老最先的边权值和,再在祖先上维护一个Size,表示集合的大小。Wikioi上的题解有个水表说不能路径压缩。。。怎么可能嘛!!!我们仔细思考一下,每一个点在一个集合中只会被压缩一次,压缩之后就会被连接到祖先上,到祖先的权值和等于到原来的父亲的权值加上原来的父亲到祖先的权值和,只需要在压缩的时候更新一下就好了。合并的时候,把两个集合(不妨设A,B)合并在一起,假如把A并入B,则把A的祖先节点连在B的祖先上,边权设为B的Size。相当于把A集合直接接在B的后面,如此,这个问题就被很好的解决了。

  代码:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <algorithm>
 5 
 6 using namespace std;
 7 struct spaceship
 8 {
 9     int father;
10     int len,maxlen;
11     
12     spaceship(){len=0;maxlen=1;}
13 }sp[30010];
14 
15 int get(int n)
16 {
17     if (sp[n].father==n) return n;
18     int m=get(sp[n].father);
19     sp[n].len+=sp[sp[n].father].len;
20     sp[n].father=m;
21     return sp[n].father;
22 }
23 
24 void marry(int n,int m)
25 {
26     int nn=get(n);
27     int mm=get(m);
28     sp[nn].father=mm;
29     sp[nn].len+=sp[mm].maxlen;
30     sp[mm].maxlen+=sp[nn].maxlen;
31 }
32 
33 int main()
34 {
35     int n;
36     scanf("%d",&n);
37     for (int i=1;i<=30000;i++) sp[i].father=i;
38     for (int i=1;i<=n;i++)
39     {
40         char ch;
41         int j,k;
42         char s[5];
43         scanf("%s",s);
44         if (s[0]=='M')
45         {
46             scanf("%d%d",&j,&k);
47             marry(j,k);
48         }
49         else
50         {
51             scanf("%d%d",&j,&k);
52             if (get(j)==get(k))
53             {
54                 printf("%d\n",abs(sp[j].len-sp[k].len)-1);
55             }
56             else
57                 printf("-1\n");
58         }
59     }    
60     return 0;
61 }
View Code

  上面这道是裸题,下面有一个看起来不裸的的水题:

  加权并查集弱题——食物链

  初一看这道题似乎和并查集关系不大,但是仔细观察可以发现,不同的个体存在一些关系。我们将物种设为0,1,2三种,则给出的条件分为两种——1.两个个体数字(物种)相同。2,个体A数字=(个体B数字+1)%3 。是否有了一点发现呢,如果是相同的数字,并且不属于同一个集合,就想合并办法让他们之间的权值变为0(mod 3),同理,可以让其数字变为1或2(mod 3),只需要在路径压缩的时候取模就行了。水题解决,代码用下一道的吧。。。

  原创题——魏总数星星

  

魏总数星星

star.cpp/c/pas

【描述】

魏总,也就是DP魏,非常喜欢星星,有一天他躺在草坪上数星星。天上共有i颗星星,魏总把天空分成了K个扇形,绕着天空的中心——月亮排布。月亮看见魏总喜欢星星,非常不爽,她就想考一下魏总。月亮给出n队星星的相互关系,形如a b p表示b星星在a星星所在扇形区域的顺时针方向第p个扇形内(0<=p<=k)(p==0时表示在同一个扇形内)。最后月亮要询问m次,形如a b表示询问a b两星是否在一个扇形内,是则输出“Yes”,不是则输出“No”,不知道则输出“Unknown”。由于月亮看魏总喜欢星星变得心情急躁,可能有一些关系与前面的关系矛盾,则这些关系无效。月亮说如果不能把她的所有询问答对就要发出强光,让魏总看不到星星,而本来是大神的魏总因为想见到星星不能编程,只有把这个艰巨的任务交给你了。

【输入】

第一行四个整数iknm表示i个星星,k个扇形,n个关系,m个询问。

接下来n行,每行三个整数a b p 表示表示b星星在a星星所在扇形区域的顺时针方向第p个扇形内。

接下来m行,每行两个整数a b表示询问ab是否在同一个扇形内。

【输出】

m行,每行为“Yes”或“No”或“Unknown”对应每一个询问

【样例输入】

5 5 3 3

1 2 1

2 4 2

4 5 2

1 2

3 4

1 5

【样例输出】

No

Unknown

Yes

【数据范围】

20%,魏总数不超过100个星星,月亮询问不超过100次,天空被分成不超过10个区域。

50%,魏总数不超过4000个星星,月亮询问不超过4000次,天空被分成不超过1000个区域。

100%,魏总数不超过100000个星星,月亮询问不超过100000次,天空被分成不超过10000个区域,关系数少于200000

 

其实很容易发现这道题和食物链是一模一样的,只不过把边权值改为了比较大的数,只需稍微改一下代码即可,代码如下:

 

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <algorithm>
 5 
 6 using namespace std;
 7 struct star
 8 {
 9     int father;
10     int len;
11 
12     star()
13     {
14         father=0;
15         len=0;
16     };
17 }st[100010];
18 int mod;
19 int get(int now)
20 {
21     if (st[now].father==now)
22     {
23         return now;
24     }
25     int f=get(st[now].father);
26     st[now].len=(st[now].len+st[st[now].father].len)%mod;
27     if (st[now].len==0) st[now].len=mod;
28     st[now].father=f;
29     return f;
30 }
31 void marry(int a,int b,int l)
32 {
33     int bf=get(b);
34     int af=get(a);
35     int len=st[a].len+l;
36     len=len % mod;
37     if (l>=st[b].len)
38     {
39         st[bf].father=a;
40         st[bf].len=l-st[b].len;
41     }
42     else
43     {
44         st[bf].father=a;
45         st[bf].len=mod+l-st[b].len;
46     }
47 }
48 
49 int main()
50 {
51     freopen ("star.in","r",stdin);
52     freopen ("star.out","w",stdout);
53     int n,m,k;
54     scanf("%d%d%d%d",&n,&mod,&m,&k);
55     for (int i=1;i<=n;i++)
56     {
57         st[i].father=i;
58     }
59     int a,b,p;
60     for (int i=1;i<=m;i++)
61     {
62         scanf("%d%d%d",&a,&b,&p);
63         if (get(a)!=get(b))
64         {
65             marry(a,b,p);
66         }
67     }
68     for (int i=1;i<=k;i++)
69     {
70         scanf("%d%d",&a,&b);
71         if (get(a)!=get(b))
72         {
73             printf("Unknown\n");
74         }
75         else
76         {
77             if (st[a].len%mod==st[b].len%mod) printf("Yes\n");
78             else printf("No\n");
79         }
80     }
81     return 0;
82 }
View Code

 

总结:并查集可以处理没有分离操作的一类问题,可以在极快的时间内处理元素间相互关系的问题,好写好用,就是没什么地方用。。。