[关闭]
@TryMyEdge 2017-02-11T06:32:24.000000Z 字数 5980 阅读 827

专题四(并查集、生成树)

题解


题目

A How Many Tables

题目大意:

    有N(1<=N<=1000)个朋友,M(1<=M<=1000)条关系,第i条关系表示ai和bi是朋友(1<=ai,bi<=N,ai!=bi)。朋友(包括直接和间接)可以坐在一桌吃饭。问需要多少张桌子。
    T(1<=T<=25)组数据。

解题思路:

    并查集裸题,把每个关系的两个端点所在的并查集合并,最后看有多少个并查集就行了。
    N和M都是1000,优不优化无所谓。。。
    时间复杂度o(T*(N+M)),空间复杂度o(N)。

AC代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #define pq priority_queue
  4. #define Pi acos(-1.0)
  5. using namespace std;
  6. #define MOD 1000000007
  7. int root[1005];
  8. int findr(int x)
  9. {
  10. if(root[x]==x)
  11. return x;
  12. else
  13. return root[x]=findr(root[x]);
  14. }
  15. int main()
  16. {
  17. int T,n,m,a,b,ans;
  18. cin>>T;
  19. while(T--)
  20. {
  21. scanf("%d%d",&n,&m);
  22. for(int i=1;i<=n;i++)
  23. root[i]=i;
  24. while(m--)
  25. {
  26. scanf("%d%d",&a,&b);
  27. a=findr(a);
  28. b=findr(b);
  29. root[a]=b;
  30. }
  31. ans=0;
  32. for(int i=1;i<=n;i++)
  33. {
  34. if(root[i]==i)
  35. ans++;
  36. }
  37. printf("%d\n",ans);
  38. }
  39. return 0;
  40. }

B Corporative Network

题目大意:

    有N(5<=N<=20000)个点,M(M<=200000)个操作,M不直接给出。
    操作分三种:
    (1)I x y,表示x(保证x之前没有父亲)的父亲变为y
    (2)E x,询问x到根的距离(儿子到父亲的距离为两个点编号差的绝对值)
    (3)O,表示该组数据结束
    T组数据。

解题思路:

    维护每个点到当前根的距离,初始状态每个点的根为自己,距离为0,如果遇到操作(1)则更将x的根改为y,距离改为x和y的差的绝对值。查询的时候再修改根的过程中同时修改这条链上每个点和根的距离。
    压缩路径。
    时间复杂度o(N+M),空间复杂度o(N)。

AC代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cmath>
  4. #include<algorithm>
  5. #define pq priority_queue
  6. #define Pi acos(-1.0)
  7. using namespace std;
  8. #define MOD 1000000007
  9. int root[20005];
  10. int num[20005];
  11. int findr(int x)
  12. {
  13. int y;
  14. if(root[x]==x)
  15. return x;
  16. else
  17. {
  18. y=root[x];
  19. root[x]=findr(root[x]);
  20. num[x]+=num[y];
  21. return root[x];
  22. }
  23. }
  24. int main()
  25. {
  26. int T,n,m,a,b,ans;
  27. char c[6];
  28. cin>>T;
  29. while(T--)
  30. {
  31. scanf("%d%d",&n);
  32. for(int i=1;i<=n;i++)
  33. {
  34. root[i]=i;
  35. num[i]=0;
  36. }
  37. while(scanf("%s",c),c[0]!='O')
  38. {
  39. if(c[0]=='I')
  40. {
  41. scanf("%d%d",&a,&b);
  42. root[a]=b;
  43. num[a]=abs(a-b)%1000;
  44. }
  45. else
  46. {
  47. scanf("%d",&a);
  48. findr(a);
  49. printf("%d\n",num[a]);
  50. }
  51. }
  52. }
  53. return 0;
  54. }

C 卿学姐与诡异村庄

题目大意:

    一个村庄里有N(1<=N<=100000)个人,其中不是好人就是坏人。好人只会说真话,坏人只会说假话。每个人会指认一名其他人是好人或者坏人。问是否存在一种分类满足所有的指控。

解题思路:

    每个人有两种可能性:好人或者坏人。
    若A说B是好人,则A如果说假话,A和B同为坏人,若A说真话,则A和B同为好人。同理,若A说B是坏人,则A和B一定一好一坏。所以对于这每条信息的两种情况,分别把对应的两个可能性所处的并查集合并。最后检查是否有某个人为好人和为坏人的可能性处于一个并查集,也就是某人既是好人又是坏人,判断是否有分类符合要求。
    时间复杂度o(N),空间复杂度o(N)。

AC代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<queue>
  4. #define pq priority_queue
  5. #define Pi acos(-1.0)
  6. using namespace std;
  7. #define MOD 1000000007
  8. int root[200005];
  9. int findr(int x)
  10. {
  11. if(root[x]==x)
  12. return x;
  13. else
  14. return root[x]=findr(root[x]);
  15. }
  16. int main()
  17. {
  18. int n,a,b,c,d;
  19. bool ans;
  20. cin>>n;
  21. for(int i=1;i<=n;i++)
  22. {
  23. root[i]=i;
  24. root[i+100001]=i+100001;
  25. }
  26. for(int i=1;i<=n;i++)
  27. {
  28. scanf("%d%d",&c,&d);
  29. if(d==1)
  30. {
  31. a=findr(i);
  32. b=findr(c);
  33. root[a]=b;
  34. a=findr(i+100001);
  35. b=findr(c+100001);
  36. root[a]=b;
  37. }
  38. else
  39. {
  40. a=findr(i);
  41. b=findr(c+100001);
  42. root[a]=b;
  43. a=findr(i+100001);
  44. b=findr(c);
  45. root[a]=b;
  46. }
  47. }
  48. ans=0;
  49. for(int i=1;i<=n;i++)
  50. ans|=findr(i)==findr(i+100001);
  51. if(ans)
  52. printf("One face meng bi\n");
  53. else
  54. printf("Time to show my power\n");
  55. return 0;
  56. }

D 食物链

题目大意:

    动物分ABC三种,A吃B、B吃C、C吃A。有N(1<=N<=50000)个动物,K(0<=K<=100000)句话,每句话描述了某两个动物间的关系是属于同一种类或者前者吃后者。一句话如果提到了这N个动物之外的动物、描述自己吃自己或者与之前的话矛盾,这句话就是假话,否则就是真话。问假话的总数。

解题思路:

    和C题的思路有些类似。每种动物有ABC三种可能性。如果用←→把共存的可能性连接起来,那么我们就可以用x.A←→y.A、x.B←→y.B、x.C←→y.C表示x和y属于同一种类,用x.A←→y.B,x.B←→y.C、x.C←→y.A表示x吃y。对于每一句话,先查询是否存在某个与这句话的可能性属于同一个并查集,不存在的话就把对应的可能性所属的并查集合并。
    时间复杂度o(k+N),空间复杂度o(N)。

AC代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #define pq priority_queue
  4. #define Pi acos(-1.0)
  5. using namespace std;
  6. #define MOD 1000000007
  7. int root[150010];
  8. int findr(int x)
  9. {
  10. if(root[x]==x)
  11. return x;
  12. else
  13. return root[x]=findr(root[x]);
  14. }
  15. int main()
  16. {
  17. int n,a,b,c,d,e,f,flag;
  18. int ans=0,k;
  19. cin>>n>>k;
  20. for(int i=1;i<=n;i++)
  21. {
  22. root[i]=i;
  23. root[i+50001]=i+50001;
  24. root[i+100002]=i+100002;
  25. }
  26. for(int i=1;i<=k;i++)
  27. {
  28. scanf("%d%d%d",&flag,&a,&b);
  29. if(a>n || b>n)
  30. ans++;
  31. else
  32. {
  33. c=findr(a+50001);
  34. d=findr(b+50001);
  35. e=findr(a+100002);
  36. f=findr(b+100002);
  37. a=findr(a);
  38. b=findr(b);
  39. if(flag==1)
  40. {
  41. if(a==d || a==f)
  42. ans++;
  43. else
  44. {
  45. root[a]=b;
  46. root[c]=d;
  47. root[e]=f;
  48. }
  49. }
  50. else
  51. {
  52. if(a==b || a==f)
  53. ans++;
  54. else
  55. {
  56. root[a]=d;
  57. root[c]=f;
  58. root[e]=b;
  59. }
  60. }
  61. }
  62. }
  63. printf("%d\n",ans);
  64. return 0;
  65. }

E Dark roads

题目大意:

    有m(1<=m<=200000)个点,由n(m-1<=n<=200000)条无向边连接,第i条边有一个权值zi,原图保证联通。在保证图联通的情况下,删去其中一部分边,求被删去的边的总权值最大为多少。
    多组数据。保证所有边的总权值不超过2^31。

解题思路:

    最小生成树裸题。
    如果当前图不是一颗树,那么一定有环,环上至少一条边可以删去。于是最后删完剩下的图一定是原图的一颗生成树。于是问题就转化为求总权值最小的生成树,再用所有边的总权值减去最小生成树的总权值。最小生成树的方法求prim或者用kruskal都行,我这里用的是kruskal。
    小细节:注意点的编号是从0到m-1。
    时间复杂度o(n*logn),空间复杂度o(n+m)。

AC代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cmath>
  4. #include<cstring>
  5. #include<algorithm>
  6. #include<queue>
  7. #define pq priority_queue
  8. #define Pi acos(-1.0)
  9. using namespace std;
  10. #define MOD 1000000007
  11. struct Edge
  12. {
  13. int u,v;
  14. long long l;
  15. friend bool operator <(Edge a,Edge b)
  16. {
  17. return a.l<b.l;
  18. }
  19. }e[200005];
  20. int root[200005];
  21. int findr(int x)
  22. {
  23. if(root[x]==x)
  24. return x;
  25. else
  26. return root[x]=findr(root[x]);
  27. }
  28. int main()
  29. {
  30. int n,m;
  31. long long ans;
  32. while(cin>>n>>m,n||m)
  33. {
  34. ans=0;
  35. for(int i=0;i<n;i++)
  36. root[i]=i;
  37. for(int i=0;i<m;i++)
  38. scanf("%d%d%lld",&e[i].u,&e[i].v,&e[i].l);
  39. sort(e,e+m);
  40. for(int i=0;i<m;i++)
  41. {
  42. if(findr(e[i].u)==findr(e[i].v))
  43. ans+=e[i].l;
  44. else
  45. root[findr(e[i].u)]=findr(e[i].v);
  46. }
  47. printf("%lld\n",ans);
  48. }
  49. return 0;
  50. }

F Design Tutorial: Inverse the Problem

题目大意:

    给出一个n*n(1<=n<=2000)的矩阵D,Dij(0<=Dij<=10^9)表示点i和点j的最短距离,问是否有一个由n个点,n-1条正整数权值边组成的树,满足这个邻接矩阵。

解题思路:

    如果D是某个树对应的邻接矩阵,因为每条边的权值都是正的,那么这树上的n-1条边也一定表示在邻接矩阵中,于是我们用这n*n条边(实际上只要取n*(n-1)/2条)进行最小生成树,然后根据生成树处理出另一个邻接矩阵与原矩阵进行比对看是不是一样就行了。
    我对代码进行了一些修改,反正要跑出n*n个点对的距离,n^2的复杂度是不可避免的,所以我干脆在合并点集的时候 就将两个点集中点配对的距离通过新加的这条边进行计算,于是就不需要并查集了,可以直接维护点的根。
    小细节:当答案为no的时候,有可能会出现某些点对之间的距离因为由几条边的权值相加导致爆int。
    时间复杂度o(n^2*log(n^2)),空间复杂度o(n^2)。

AC代码:

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cmath>
  4. #include<cstring>
  5. #include<algorithm>
  6. #include<queue>
  7. #define pq priority_queue
  8. #define Pi acos(-1.0)
  9. using namespace std;
  10. #define MOD 1000000007
  11. struct Edge
  12. {
  13. int u,v;
  14. long long l;
  15. friend bool operator <(Edge a,Edge b)
  16. {
  17. return a.l<b.l;
  18. }
  19. }e[4000005],temp;
  20. int root[2005];
  21. int v[2005][2005],lens[2005];
  22. long long ans[2005][2005],dis[2005][2005];
  23. int main()
  24. {
  25. int n,cnt=0,a,b,c,d;
  26. bool flag=0;
  27. cin>>n;
  28. for(int i=1;i<=n;i++)
  29. {
  30. lens[i]=1;
  31. v[i][0]=i;
  32. root[i]=i;
  33. for(int j=1;j<=n;j++)
  34. {
  35. scanf("%lld",&dis[i][j]);
  36. if(i!=j)
  37. {
  38. if(!dis[i][j])
  39. flag=1;
  40. if(i<j)
  41. {
  42. temp.u=i;
  43. temp.v=j;
  44. temp.l=dis[i][j];
  45. e[cnt++]=temp;
  46. }
  47. }
  48. else
  49. {
  50. if(dis[i][j])
  51. flag=1;
  52. }
  53. }
  54. }
  55. sort(e,e+cnt);
  56. for(int t=0;t<cnt;t++)
  57. {
  58. if(root[e[t].u]!=root[e[t].v])
  59. {
  60. a=root[e[t].u];
  61. b=root[e[t].v];
  62. c=lens[a];
  63. d=lens[b];
  64. for(int i=0;i<c;i++)
  65. {
  66. for(int j=0;j<d;j++)
  67. {
  68. ans[v[a][i]][v[b][j]]=ans[v[b][j]][v[a][i]]=ans[v[a][i]][e[t].u]+e[t].l+ans[e[t].v][v[b][j]];
  69. if(ans[v[a][i]][v[b][j]]!=dis[v[a][i]][v[b][j]] || ans[v[b][j]][v[a][i]]!=dis[v[b][j]][v[a][i]])
  70. flag=1;
  71. }
  72. root[v[a][i]]=b;
  73. v[b][lens[b]++]=v[a][i];
  74. }
  75. }
  76. }
  77. if(flag)
  78. printf("NO\n");
  79. else
  80. printf("YES\n");
  81. return 0;
  82. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注