PAT:并查集

1013. 求地图中连通城市块的个数

思路
题目给出一张图,以及几条边。要求当去掉其中的一个顶点后为了使剩下的顶点可以连通需要增加多少条边。
实际上就是求图中独立子图的个数,可以用到并查集。

#include <iostream>  
#include <vector>  
using namespace std;  

int x[540000],y[540000];
int pre[1005];
//查找加压缩
int find(int a) {
    if(pre[a] != a)
        pre[a] = find(pre[a]);
    return pre[a];
}
//合并
void join(int a,int b) {
    int rootA = find(a);
    int rootB = find(b);
    if(rootA != rootB) pre[rootB] = rootA;
}

int main()
{
    int n,m,k,u,c=0,result=0;
    cin>>n>>m>>k;

    //初始化pre
    for (int i = 0; i <= 1005; i++)
        pre[i] = i;
    //边连通的点分别保存在x和y中
    while(m--) {
        cin>>x[c]>>y[c];
        c++;
    }
    while(k--) {
        result=0;
        cin>>u;
        //将不涉及点u且彼此连通的点连通成一个子图
        for (int i = 0; i < c; i++) {
            if(x[i]!=u && y[i]!=u)
                join(x[i],y[i]);
        }
        for (int i = 1; i <= n; i++) {
            if(i != u && pre[i] == i) result++;//计算独立的子图(根节点)的个数
            pre[i] = i;//重新初始化pre,以备下一次计算
        }
        cout<<result - 1<<endl;
    }
    return 0;  
}

通过找到一伙人及其头目

『gang』翻译过来是『一伙人』。gang 的定义是一群人,至少有 3 个人,这群人中每个人之间都通过通话相连,且整个群体的通话时长超过一个阈值。整个 gang 的团体中,拥有的电话时长最长的人就是头目了。
思路
并查集的变形,要记录每次通话的信息。

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <algorithm>
using namespace std;

typedef struct Node
{
  int weight, parent;
}Node;
typedef struct Call
{
  string a, b;
  int t;
}Call;
typedef struct Gang
{
  int head, num, sum, maxw;
  Gang(){head=-1;num=0;sum=0;maxw=-1;}
};
vector<Node> p;//disjoint set
vector<Call> call;
map<string, int> name2id;
map<int, string> id2name;
set<string> name;
int n, k;
int realn;//number of distinct node
void InitSet()
{
  p.resize(realn);
  for (int i = 0; i < realn; ++i)
  {
    p[i].parent = i; p[i].weight = 0;
  }
}
void CompressSet(int top, int x)
{
  if (top != p[x].parent)
  {
    CompressSet(top, p[x].parent);
    p[x].parent = top;
  }
}
int FindSet(int x)
{
  if (x != p[x].parent)
  {
    int top = FindSet(p[x].parent);
    CompressSet(top, x);
  }
  return p[x].parent;
}
void UnionSet(int x, int y)
{
  int a = FindSet(x);
  int b = FindSet(y);
  p[a].parent = b;
}
int main()
{
  while(scanf("%d%d",&n,&k)!=EOF)
  {
    call.resize(n);
    name.clear();
    for (int i = 0; i < n; ++i)
    {
      cin>>call[i].a; cin>>call[i].b; cin>>call[i].t;
      name.insert(call[i].a); name.insert(call[i].b); 
    }
    //get the person number
    realn = name.size();
    name2id.clear();
    id2name.clear();
    set<string>::iterator it1;
    int id = 0;
    for (it1=name.begin(); it1!=name.end(); it1++,++id)
      name2id[*it1] = id, id2name[id]=*it1;
    //build disjoint set
    InitSet();
    for (int i = 0; i < call.size(); ++i)
    {
      int u = name2id[call[i].a]; int v = name2id[call[i].b]; int t = call[i].t;
      //printf("%d %d %d\n", u, v, t);
      p[u].weight += t; p[v].weight += t;
      UnionSet(u, v);
    }
    //collect all set
    map<int,Gang> allSet;//father and weight of set
    map<int,Gang>::iterator it;
    for (int i = 0; i < realn; ++i)
    {
      int top = FindSet(i);
      it = allSet.find(top);
      if (it != allSet.end())
      {
        allSet[top].sum += p[i].weight;
        allSet[top].num++;
        if (p[i].weight > allSet[top].maxw)
        {
          allSet[top].head = i;
          allSet[top].maxw = p[i].weight;
        }

      }
      else
      {
        Gang tmp;
        tmp.head = i; tmp.maxw = p[i].weight; 
        tmp.num = 1;  tmp.sum = p[i].weight;
        allSet[top] = tmp;
      }
    }
    //threthold gang
    std::vector<Gang> gang;
    for (it = allSet.begin(); it!=allSet.end(); it++)
      if (it->second.sum > k*2 && it->second.num > 2) 
        gang.push_back(it->second);
    //output
    vector<pair<string, int>> head;
    for (int i = 0; i < gang.size(); ++i)
      head.push_back(make_pair(id2name[gang[i].head],gang[i].num));
    sort(head.begin(), head.end());
    printf("%d\n",head.size());
    for (int i = 0; i < head.size(); ++i)
      cout<<head[i].first<<" "<<head[i].second<<endl;
  }
  return 0;
}

1114. 家庭财产

给定每个人的家庭成员和其自己名下的房产,请你统计出每个家庭的人口数、人均房产面积及房产套数。
思路
用并查集。
分别用两个结构体数组,data[]用来接收每组数据,接收的时候顺便实现了并查集的操作union,另一个数组ans[]用来计算并存储最后的答案。
因为要计算家庭人数,所以用visit标记所有出现过的结点,对于每个结点的父结点,people++统计人数。
标记flag == true,计算true的个数就可以知道一共有多少个家庭。
排序后输出前cnt个就是所求答案。


通过这题,可以加深对并查集的理解,并查集会忽略元素之间的从属关系,只在乎彼此之间有没有联系。因此可以用于城市连通问题、家族树这些场合。

#include <cstdio>
#include <algorithm>
using namespace std;

struct DATA {
    int id, fid, mid, num, area;
    int cid[10];
}data[1005];
struct node {
    int id, people;
    double num, area;
    bool flag;
}ans[10000];
int father[10000];
bool visit[10000];
int find(int x) {
    while(x != father[x])
        x = father[x];
    return x;
}
void Union(int a, int b) {
    int faA = find(a);
    int faB = find(b);
    if(faA > faB)
        father[faA] = faB;
    else if(faA < faB)
        father[faB] = faA;
}
int cmp1(node a, node b) {
    if(a.area != b.area)
        return a.area > b.area;
    else
        return a.id < b.id;
}
int main() {
    int n, k, cnt = 0;
    scanf("%d", &n);
    for(int i = 0; i < 10000; i++) father[i] = i;
    for(int i = 0; i < n; i++) {
        scanf("%d %d %d %d", &data[i].id, &data[i].fid, &data[i].mid, &k);
        visit[data[i].id] = true;
        if(data[i].fid != -1) {
            visit[data[i].fid] = true;
            Union(data[i].fid, data[i].id);
        }
        if(data[i].mid != -1) {
            visit[data[i].mid] = true;
            Union(data[i].mid, data[i].id);
        }
        for(int j = 0; j < k; j++) {
            scanf("%d", &data[i].cid[j]);
            visit[data[i].cid[j]] = true;
            Union(data[i].cid[j], data[i].id);
        }
        scanf("%d %d", &data[i].num, &data[i].area);
    }
    for(int i = 0; i < n; i++) {
        int id = find(data[i].id);
        ans[id].id = id;
        ans[id].num += data[i].num;
        ans[id].area += data[i].area;
        ans[id].flag = true;
    }
    for(int i = 0; i < 10000; i++) {
        if(visit[i])
            ans[find(i)].people++;
        if(ans[i].flag)
            cnt++;
    }
    for(int i = 0; i < 10000; i++) {
        if(ans[i].flag) {
            ans[i].num = (double)(ans[i].num * 1.0 / ans[i].people);
            ans[i].area = (double)(ans[i].area * 1.0 / ans[i].people);
        }
    }
    sort(ans, ans + 10000, cmp1);
    printf("%d\n", cnt);
    for(int i = 0; i < cnt; i++)
        printf("%04d %d %.3f %.3f\n", ans[i].id, ans[i].people, ans[i].num, ans[i].area);
    return 0;
}

1118. 森林中的鸟

题意:一幅画里面的鸟为同一棵树上的,问有多少棵树和多少只鸟,以及对于两只鸟判断是否在同一个树上。
思路
每次得到鸟的序列时,先将序列排序,然后从头至尾,两个两个进行join操作。这样就能保证同一棵树上的鸟即使出现在不同的照片上,其pre[i]都等于该树上鸟中最小的编号。
考察pre数组中pre[i]==i的个数,即为树的棵数。
注意
单纯的并查集只能得到独立子图的个数,本题中是树的数目。但是,(即使经过了路径压缩)它不能保证同属于一个独立子图的节点的前导节点都相同。在这种情况下,如果还要判断两只鸟是否在同一棵树上,还要再进行一次find(i)操作。

#include<iostream>
#include<vector>
#include<set>
#include<string>
#include<algorithm>

using namespace std;
int pre[10005]={0};
int find(int a) {
    if(pre[a] == a) return pre[a];
    pre[a]=find(pre[a]);
    return pre[a];
}
void join(int a,int b) {
    int p1=find(a);
    int p2=find(b);
    if(p1<p2)   pre[p2]=p1;
    else if(p1>p2)  pre[p1]=p2;
}

int main() {
    int n,k;
    int num=0;
    int ans=0;
    cin>>n;
    for(int i=0;i<n;i++) {
        cin>>k;
        vector<int> v(k);
        for(int j=0;j<k;j++)    {
            cin>>v[j];
            if(pre[v[j]]==0) pre[v[j]]=v[j];
        }
        for(int j=1;j<k;j++)    join(v[0],v[j]);
    }
    for(int i=0;i<10005;i++) {
        if(pre[i]!=0)  {
            find(i);
            num++;
            if(pre[i]==i)   ans++;
        }
    }
    cout<<ans<<' '<<num<<endl;
    int q;
    cin>>q;
    for(int i=0;i<q;i++) {
        int t1,t2;
        cin>>t1>>t2;
        if(pre[t1]==pre[t2]) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值