PAT A 1034[图的遍历/并查集]

注意点:编号转换函数;DFS中记录整个连通图的点权之和,总边权就是总点权的一半。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2010;
unordered_map<int, string> idTostr; //编号->名字
unordered_map<string, int> strToid; //名字->编号
map<string, int> Gang; //团伙头目->团伙人数
int n, k, weight, numPerson = 0; //numPerson记录总人数
int G[maxn][maxn] = {0}, w[maxn] = {0};
bool vis[maxn] = {false};
int change(string s) //编号转换函数
{
    if(strToid.find(s) != strToid.end()) { //判断s是否已经记录过
    	//注意此处不能是if(strToid[s] != 0),因为0编号是存在的
        return strToid[s];
    } else {
        strToid[s] = numPerson;
        idTostr[numPerson] = s;
        return numPerson++; //先返回编号,总人数再++
    }
}
//nowVisit为当前访问的顶点编号,head为团伙头目编号,numMember为连通图人数
//totalPValue为连通图总点权
void DFS(int nowVisit, int& head, int& numMember, int& totalPValue)
{ 
    numMember++;
    totalPValue += w[nowVisit];
    vis[nowVisit] = true;
    if(w[nowVisit] > w[head]) head = nowVisit; //更新head编号
    for(int i = 0; i < numPerson; ++i) {
        if(G[nowVisit][i]) {
            if(!vis[i]) DFS(i, head, numMember, totalPValue);
        }
    }
}
void DFSTrave() //遍历整个图
{
    for(int i = 0; i < numPerson; ++i) {
        if(!vis[i]) { //对每个没访问过的顶点
            int head = i, numMember = 0, totalPValue = 0;
            DFS(i, head, numMember, totalPValue);//访问其所在连通块,并计算head, numMember, totalPValue的值
            if(numMember > 2 && totalPValue > 2 * k) {
                Gang[idTostr[head]] = numMember;
            }
        }
    }
}
int main()
{
    scanf("%d %d", &n, &k);
    for(int i = 0; i < n; ++i) {
        string s1, s2;
        cin >> s1 >> s2;
        scanf("%d", &weight);
        int u = change(s1), v = change(s2);
        G[u][v] = G[v][u] = 1;
        w[u] += weight;
        w[v] += weight;
    }
    DFSTrave();
    printf("%d\n", Gang.size());
    for(auto it = Gang.begin(); it != Gang.end(); ++it) {
        cout << it->first << " " << it->second << endl;
    }
    return 0;
}

并查集做法:
注意点:不需要在Union函数中加入额外判断,先简单合并构造并查集,然后利用set来对每个并查集的所有结点遍历,把点权最大的编号赋值给head

#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int n, k, father[N], w[N], numPerson = 0;
unordered_map<string, int> str_int;
unordered_map<int, string> int_str;
map<string, int> Gang;
int findFather(int x)
{
    if(x == father[x]) return x;
    else {
        int f = findFather(father[x]);
        father[x] = f;
        return f;
    }
}
void Union(int a, int b)
{
    int faA = findFather(a);
    int faB = findFather(b);
    if(faA != faB) father[faB] = faA;
}
int change(string s)
{
    if(str_int.find(s) != str_int.end()) {
        return str_int[s];
    } else {
        str_int[s] = numPerson;
        int_str[numPerson] = s;
        return numPerson++;
    }
}
void init() {
    for(int i = 0; i < N; ++i) {
        father[i] = i;
        w[i] = 0;
        num[i] = 0;
    }
}
int main()
{
    init();
    int weight;
    scanf("%d %d", &n, &k);
    unordered_map<int, unordered_set<int> > mp; //根结点->并查集
    for(int i = 0; i < n; ++i) {
        string s1, s2;
        cin >> s1 >> s2;
        int u = change(s1), v = change(s2);
        scanf("%d", &weight);
        Union(u, v);
        w[u] += weight;
        w[v] += weight;
    }
    for(int i = 0; i < numPerson; ++i) {
        int f = findFather(i);
        mp[f].insert(i);
    }
    for(auto it = mp.begin(); it != mp.end(); ++it) { //对每个根结点
        int head = it->first, total = 0; //head初值为根结点编号,total记录总点权
        //遍历并查集中的每个元素
        for(auto itr = it->second.begin(); itr != it->second.end(); ++itr) {
            total += w[*itr]; //计算总点权
            if(w[*itr] > w[head]) { //更新head
                head = *itr;
            }
        }
        if(it->second.size() > 2 && total > 2 * k) {
            Gang[int_str[head]] = it->second.size();
        }
    }
    printf("%d\n", Gang.size());
    for(auto it = Gang.begin(); it != Gang.end(); ++it)
        cout << it->first << " " << it->second << endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值