1. inline void init(int n)
  2. {
  3. for (int i = 1; i <= n; ++i)
  4. {
  5. fa[i] = i;
  6. rank[i] = 1;
  7. }
  8. }
  9. int find(int x)
  10. {
  11. return x == fa[x] ? x : (fa[x] = find(fa[x]));
  12. }
  13. inline void merge(int i, int j)
  14. {
  15. int x = find(i), y = find(j);
  16. if (rank[x] <= rank[y])
  17. fa[x] = y;
  18. else
  19. fa[y] = x;
  20. if (rank[x] == rank[y] && x != y)
  21. rank[y]++;
  22. }

例题

(洛谷P1551)亲戚
洛谷P3958 奶酪)

L2-007 家庭房产 (25 分)

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int co;//关系的数量
  4. int n;//数据量
  5. const int N = 1e4+5;
  6. struct edfe{
  7. int a,b;//关系先记入这里面
  8. }e[N];
  9. bool st[N];//有一个人的家庭,需要特殊处理
  10. //并查集
  11. //家庭代表(这里需要是id最小的),人数,房屋数量,房屋面积
  12. int fa[N],peo[N],avgs[N],avga[N];
  13. int find(int x){
  14. if(fa[x]!=x)fa[x]=find(fa[x]);
  15. return fa[x];
  16. }
  17. void unionf(int a, int b){
  18. int faa=find(a),fbb = find(b);
  19. if(faa!=fbb){//还没有记为一个家庭的话
  20. fa[max(faa,fbb)] = min(faa,fbb);//将id较小的设置为父亲
  21. peo[min(faa,fbb)]+=peo[max(faa,fbb)];//将人数加到id较小的人数里面存着
  22. avgs[min(faa,fbb)]+=avgs[max(faa,fbb)];//房屋数量,同上
  23. avga[min(faa,fbb)]+=avga[max(faa,fbb)];//房屋面积,同上
  24. }
  25. }
  26. struct family{
  27. int id,cnt,avgs,avga;
  28. //重载运算符,为了下面的sort
  29. bool operator <(family x){
  30. if(x.cnt*avga==cnt*x.avga){
  31. return id<x.id;
  32. }
  33. return x.cnt*avga>cnt*x.avga;
  34. }
  35. };
  36. int main()
  37. {
  38. ios::sync_with_stdio(false);
  39. //初始化
  40. for(int i =0;i<N;i++){
  41. fa[i]=i,peo[i]=1;
  42. }
  43. cin>>n;
  44. int id, father,mother, k;
  45. for(int i =0;i<n;i++){
  46. cin>>id>>father>>mother>>k;
  47. if(father!=-1)e[co++]={id,father};
  48. if(mother!=-1)e[co++]={id,mother};
  49. st[id]=true;//有些家庭可能就只有一个人,需要特殊标记
  50. int kid;
  51. for(int j=0;j<k;j++){
  52. cin>>kid;
  53. e[co++]={id,kid};
  54. }
  55. cin>>avgs[id]>>avga[id];
  56. }
  57. //开始处理刚刚接收的各个关系
  58. for(int i =0;i<co;i++){
  59. int a=e[i].a,b=e[i].b;
  60. st[a]=st[b]=true;
  61. unionf(a,b);
  62. }
  63. vector<family> ans;
  64. for(int i =0;i<N;i++){
  65. //如果这个人存在,并且是家庭代表,就加入ans
  66. if(st[i] && fa[i]==i){
  67. ans.push_back({i,peo[i],avgs[i],avga[i]});
  68. }
  69. }
  70. sort(ans.begin(),ans.end());
  71. cout<<ans.size()<<endl;
  72. for(auto t:ans)
  73. printf("%04d %d %.3lf %.3lf\n",t.id,t.cnt,(double)t.avgs/t.cnt,(double)t.avga/t.cnt);
  74. return 0;
  75. }

巧妙利用并查集——L2-013 红色警报 (25 分)

用并查集,先计算出开始时父节点指向自己的点(也就是这一个连通块的代表),数量记为num(连通块的数量),随后每次输入城市都重新计算这类点的数量numx与初始值比较,如果numx>num说明连通性改变了,每次都要更新num的值为numx
详细看注释。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define ll long long
  4. //并查集
  5. ll fa[505]; //连通的记录在一块,代表随意,只是为了方便算 块数
  6. ll lost[505] = {0};//记录被攻陷的城池
  7. struct e{//边的关系
  8. ll a,b;
  9. };
  10. vector<e> ev;
  11. //初始化fa
  12. void init(){
  13. for(ll i =0;i<505;i++){
  14. fa[i] = i;
  15. }
  16. }
  17. ll find(ll a){
  18. return a == fa[a] ? a : (fa[a] = find(fa[a]));
  19. }
  20. void merge(ll x,ll y){
  21. ll a = find(x);
  22. ll b = find(y);
  23. if(a != b){
  24. fa[a] = b;
  25. }
  26. }
  27. int main()
  28. {
  29. ios::sync_with_stdio(false);
  30. ll n,m;cin>>n>>m;
  31. init();
  32. ev.resize(m);
  33. for(int i = 0;i<m;i++){
  34. ll a,b;
  35. cin>>a>>b;
  36. ev[i] = {a,b};
  37. merge(a,b);
  38. }
  39. ll num = 0;
  40. for(ll i = 0 ;i<n;i++){
  41. if(fa[i] == i)//代表自己,说明自己就是那一块的代表
  42. num++;
  43. }
  44. ll k;cin>>k;
  45. // cout<<"k="<<k<<"\n";
  46. while(k--){
  47. ll x;cin>>x;
  48. lost[x] = 1;
  49. init();
  50. ll numx = 0;
  51. for(int i =0;i<m;i++){
  52. if(lost[ev[i].a]==0 && lost[ev[i].b]==0){//操作在ev中记录过的
  53. merge(ev[i].a,ev[i].b);
  54. }
  55. }
  56. //再算一遍有几个块
  57. for(int i = 0;i<n;i++){
  58. if(fa[i]==i && lost[i]==0)numx++;//自己是块代表,并且自己没给攻占
  59. }
  60. if(numx>num){//块的数量增加了的话,就说明整个国家的连通性改变了
  61. cout<<"Red Alert: City "<<x<<" is lost!"<<endl;
  62. }else{
  63. cout<<"City "<<x<<" is lost."<<endl;
  64. }
  65. if(numx==0){
  66. cout<<"Game Over."<<endl;
  67. }
  68. num=numx;//更新初始值
  69. }
  70. return 0;
  71. }

L3-003 社交集群 (30 分)

主要是每个人 都有各种兴趣爱好,只要两个人有相同的一个兴趣爱好,就能是一个社群——也就意味着 即使有两人可能完全没有一个兴趣,但有其他人作为中间人,就会成为一个社群的人。所以需要每个兴趣编号都有一个 第一人——所有后来有这个兴趣的,都是跟随他 (的父节点)—— 加入父节点也就是加入了该社群。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define ll long long
  4. #define FOR(i,n,m) for(int i =n;i<m;++i)
  5. //并查集
  6. vector<int> root;//root[i]==0时代表i不是根节点,否则存储的该社群的人数
  7. vector<int> f;//father
  8. int course[1005];//course[t] 存储第一个喜欢该课的人,后面的人都是追随他
  9. // int find(int x){
  10. // int t = x;
  11. // while(x != f[x]){
  12. // x = f[x]; //找到x社群最上面的根节点
  13. // }
  14. // //此时x已经变为原先x的根节点了
  15. // while(t != f[t]){
  16. // f[t] = x;//路上的所有兄弟都要跟随跟节点
  17. // t = f[t];
  18. // }
  19. // return x;//返回根节点
  20. // }
  21. int find(int x){
  22. return x == f[x] ? x : (f[x] = find(f[x]));
  23. }
  24. void merge(int a, int b){
  25. int fa = find(a),
  26. fb = find(b);
  27. if(fa != fb){
  28. f[fa] = fb;//将a的根节点改为b的根节点
  29. }
  30. }
  31. int _cmp(int a, int b){ //后面排序要用
  32. return a > b;
  33. }
  34. int main()
  35. {
  36. ios::sync_with_stdio(false);
  37. int n, k, t,
  38. cnt = 0;//社群数
  39. char c;//读那个冒号
  40. cin>>n;
  41. f.resize(n+1);
  42. root.resize(n+1);
  43. for(int i = 1; i <= n; i++)
  44. f[i] = i;
  45. for(int i = 1; i<= n; i++){
  46. cin>>k>>c;
  47. for(int j = 0;j<k;j++){
  48. cin>>t;
  49. if(course[t] == 0){ //说明没有前人,那i就当第一人
  50. course[t] = i;
  51. }
  52. merge(i, find(course[t]) );//i直接跟随第一人的根节点
  53. }
  54. }
  55. for(int i = 1;i<=n;i++){
  56. root[find(i)] ++;
  57. }
  58. for(int i = 1;i<=n;i++){
  59. if(root[i] != 0)
  60. cnt++;
  61. }
  62. cout<<cnt<<"\n";
  63. sort(root.begin() ,root.end(), _cmp);
  64. for(int i = 0;i<cnt;i++){
  65. cout<<root[i];
  66. if(i != cnt - 1)
  67. cout<<" ";
  68. }
  69. return 0;
  70. }