[关闭]
@morehigh 2017-02-25T03:14:44.000000Z 字数 4261 阅读 913

CQUPT 集训队专题训练(并查集/生成树)

并查集/生成树


并查集
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
需要实现的操作有:合并两个集合,判断两个元素是否属于一个集合。
这里介绍的主要是普通的并查集,很多情况下使用的并查集是需要扩展的,根据使用情况的不同,有很多差别,这里仅仅是最基本的算法。

最小生成树
给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树.
Kruskal算法 克鲁斯卡尔算法
方法:将图中边按其权值由小到大的次序顺序选取,若选边后不形成回路,则保留作为一条边,若形成回路则除去.依次选够(n-1)条边,即得最小生成树.(n为顶点数)
普里姆算法
方法:从指定顶点开始将它加入集合中,然后将集合内的顶点与集合外的顶点所构成的所有边中选取权值最小的一条边作为生成树的边,并将集合外的那个顶点加入到集合中,表示该顶点已连通.再用集合内的顶点与集合外的顶点构成的边中找最小的边,并将相应的顶点加入集合中,如此下去直到全部顶点都加入到集合中,即得最小生成树.

A - How Many Tables
题意:

  1. IgnatiusN个朋友,朋友不是相互认识,A认识BB认识CABC相互认识并可以坐在一起吃饭(同一张桌子),有M中关系,(1<=N,M<=1000),求至少需要多少张桌子

解题思路:

  1. 并查集,有多少个父亲节点就需要多少张桌子。

代码:

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <algorithm>
  5. using namespace std;
  6. int fa[1024];
  7. int find(int x)
  8. {
  9. return fa[x]==x?x:fa[x]=find(fa[x]);
  10. }
  11. int main()
  12. {
  13. int t;
  14. cin>>t;
  15. while(t--)
  16. {
  17. int n,m,a,b;
  18. scanf("%d%d",&n,&m);
  19. for(int i=1;i<=n;i++)
  20. fa[i]=i;
  21. for(int i=0;i<m;i++)
  22. {
  23. scanf("%d%d",&a,&b);
  24. int tmp;
  25. int x=find(a);
  26. int y=find(b);
  27. if(x>y)
  28. {
  29. tmp=x;
  30. x=y;
  31. y=tmp;
  32. }
  33. if(x!=y)
  34. {
  35. fa[y]=x;
  36. }
  37. }
  38. int ans=0;
  39. for(int i=1;i<=n;i++)
  40. {
  41. if(fa[i]==i)
  42. ans++;
  43. }
  44. cout<<ans<<endl;
  45. }
  46. return 0;
  47. }

B - Corporative Network
题意:

  1. N个公司,I公司与公司之间的距离是|I J|(mod 1000),有三种操作
  2. 1.E I 指的是连接到I公司的其他公司服务器的距离之和
  3. 2.I I J J公司的服务器连接到I公司
  4. 3.O 结束操作

解题思路:

  1. 连接两个节点时,将J作为I的父亲节点,|I-J|%1000求出IJ之间距离,在查找当前节点的根节点时,一边维护当前节点到根节点的距离.

代码:

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <algorithm>
  5. using namespace std;
  6. int fa[20010],dis[20010];
  7. int find(int x)
  8. {
  9. if(x!=fa[x])
  10. {
  11. int root=find(fa[x]);
  12. dis[x]+=dis[fa[x]];
  13. return fa[x]=root;
  14. }
  15. else
  16. return x;
  17. }
  18. int abs(int x)
  19. {
  20. if(x<0)
  21. return -x;
  22. else
  23. return x;
  24. }
  25. int main()
  26. {
  27. int t;
  28. cin>>t;
  29. while(t--)
  30. {
  31. int n,i,j;
  32. char ss[10];
  33. scanf("%d",&n);
  34. for(int k=0;k<=n;k++)
  35. {
  36. fa[k]=k;
  37. dis[k]=0;
  38. }
  39. while(scanf("%s",ss)&&ss[0]!='O')
  40. {
  41. if(ss[0]=='E')
  42. {
  43. scanf("%d",&i);
  44. find(i);
  45. printf("%d\n",dis[i]);
  46. }
  47. if(ss[0]=='I')
  48. {
  49. scanf("%d%d",&i,&j);
  50. fa[i]=j;
  51. dis[i]=abs(i-j)%1000;
  52. }
  53. }
  54. }
  55. return 0;
  56. }

C - 卿学姐与诡异村庄
题意:

  1. 村庄总共有N1N100000)个人,N个人相互指控,好人只会说真话,坏人只会说假话。输出N行,ai,t,如果t1,那么说明第i个人认为第ai个人是好人。如果t2,那么说明第i个人认为第ai个人是坏人。判断从这些人的指控中,满足一种分类每个人指控谁是否为好人

解题思路:

  1. AB是好人,那么可能有两种情况,若A是坏人,B也是坏人,若A是好人,B是好人
  2. AB是坏人,若A是好人,B是坏人,若A是坏人,B就是好人。
  3. 前(0N)表示好人,(N,2*N)表示坏人,并查集查找此人为好人时和坏人坏人的父节点是否相同,若相同则不满足

代码:

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <algorithm>
  5. #define hh 100000
  6. using namespace std;
  7. int fa[2*hh+10];
  8. int find(int x)
  9. {
  10. return fa[x]==x?x:fa[x]=find(fa[x]);
  11. }
  12. void solve(int x,int y)
  13. {
  14. int xx=find(x);
  15. int yy=find(y);
  16. if(xx!=yy)
  17. {
  18. fa[xx]=yy;
  19. }
  20. }
  21. int main()
  22. {
  23. int n,a,t;
  24. scanf("%d",&n);
  25. for(int i=1;i<=hh*2+9;i++)
  26. {
  27. fa[i]=i;
  28. }
  29. for(int i=1;i<=n;i++)
  30. {
  31. scanf("%d%d",&a,&t);
  32. if(t==1)
  33. {
  34. solve(i,a);
  35. solve(i+hh,a+hh);
  36. }
  37. else
  38. {
  39. solve(i+hh,a);
  40. solve(i,a+hh);
  41. }
  42. }
  43. for(int i=1;i<n;i++)
  44. {
  45. if(find(i)==find(i+hh))
  46. {
  47. printf("One face meng bi\n");
  48. }
  49. }
  50. printf("Time to show my power\n");
  51. return 0;
  52. }

D - 食物链
题意:

  1. 根据给定的N1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
  2. D=1,则表示XY是同类。
  3. D=2,则表示XY
  4. 判断假话的数目

解题思路:

  1. 带权并查集和普通并查集最大的区别在于带权并查集合并的是可以推算关系的点的集合(可以通过集合中的一个已知值推算这个集合中其他元素的值)。而一般并查集合并的意图在于这个元素属于这个集合。带权并查集相当于把“属于”的定义拓展了一下,拓展为有关系的集合。

代码:

  1. #include <iostream>
  2. #include <cstring>
  3. #include <cstdio>
  4. #include <algorithm>
  5. using namespace std;
  6. int f[50005],rank[50005];
  7. int find(int x)
  8. {
  9. if(x!=f[x])
  10. {
  11. int fx=find(f[x]);
  12. rank[x]=(rank[x]+rank[f[x]])%3;
  13. f[x]=fx;
  14. }
  15. return f[x];
  16. }
  17. int Union(int x,int y,int d)
  18. {
  19. int a=find(x);
  20. int b=find(y);
  21. if(a==b)
  22. {
  23. if((rank[y]-rank[x]+3)%3!=d)
  24. return true;
  25. else
  26. return false;
  27. }
  28. f[b]=a;
  29. rank[b]=(rank[x]-rank[y]+d+3)%3;
  30. return false;
  31. }
  32. int main()
  33. {
  34. int n,m;
  35. int d,x,y;
  36. scanf("%d%d",&n,&m);
  37. for(int i=1;i<=n;i++)
  38. {
  39. f[i]=i;
  40. rank[i]=0;
  41. }
  42. int ans=0;
  43. for(int i=0;i<m;i++)
  44. {
  45. scanf("%d%d%d",&d,&x,&y);
  46. if(x>n||y>n||d==2&&x==y)
  47. ans++;
  48. else if(Union(x,y,d-1))
  49. {
  50. ans++;
  51. }
  52. }
  53. cout<<ans<<endl;
  54. return 0;
  55. }

E - Dark roads
最小生成树 kruskal算法
题意:

  1. m个节点,n条路,1 m 200000m 1 n200000.i条路在x,y之间长度为z.保证联通的情况下,去掉一些边,求删去边的权值最大

解题思路:

  1. 求出边的权值的总和减去最小生成树的权值之和(最小生成树将每个点联通,并且保证权值之和最小),相减之后得到的值为所要删去边的和的最大值,

代码:
```

include

include

include

include

using namespace std;
int p[200005],r[200005];
struct Node
{
int p1,p2;
int w;
}edge[200005];
bool cmp(Node x,Node y)
{
return x.w }
int find(int x)
{
return p[x]==x?x:p[x]=find(p[x]);
}
void un( int a, int b )
{
if ( r[a] < r[b] )
p[a] = b;
else {
if( r[a] == r[b] )
r[a] ++;
p[b] = a;
}
}
int kruskal(int n,int m)
{
for(int i=0;i<=n;i++)
p[i]=i;
for(int i=0;i<=n;i++)
r[i]=0;
sort( edge, edge+m, cmp );
long long sum = 0;
for ( int i = 0 ; i < m ; ++ i ) {
int A = find( edge[i].p1 );
int B = find( edge[i].p2 );
if ( A != B ) {
un( A, B );
sum += edge[i].w;
}
}
return sum;

}
int main()
{
int n,m,a,b,c;
while(scanf("%d%d",&n,&m)&&m+n)
{
long long sum=0;
for(int i=0;i {
scanf("%d%d%d",&a,&b,&c);
edge[i].p1=a;
edge[i].p2=b;
edge[i].w=c;
sum+=c;
}
printf("%lld\n",sum-kruskal(n,m));
}
return 0;
}
```

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注