Learning Languages——并查集经典问题洛谷P3026

[USACO11OPEN]Learning Languages

题目描述

农夫约翰的 N ( 2 < = N < = 10 , 000 ) N(2<=N<=10,000) N(2<=N<=10,000)只奶牛标号为 1.. N 1..N 1..N,同样的有 M ( 1 < = M < = 30 , 000 ) M(1<=M<=30,000) M(1<=M<=30,000)种牛语标号为 1.. M 1..M 1..M,第i只奶牛会说 K i ( 1 < = K i < = M ) K_i(1<=K_i<=M) Ki(1<=Ki<=M)种牛语,分别为 L i 1 , L i 2 , … , L i K i ( 1 < = L i j < = M ) L_i1,L_i2,…,L_{iK_i}(1<=L_ij<=M) Li1,Li2,,LiKi(1<=Lij<=M),农夫的奶牛不是特别聪明,所以 K i K_i Ki的累加和不大于 100 , 000 100,000 100,000

两只奶牛只有当他们至少有一门语言一样的时候才可以沟通。否则这两只奶牛就需要别人来帮他们翻译才能交流。换句话说,A和B要进行沟通,他们可以通过 T 1 , T 2 , … , T k T_1,T_2,…,T_k T1,T2,,Tk来传递,比如A和 T 1 , T 1 T_1,T_1 T1,T1 T 2 , … , T k T_2,…,T_k T2,,Tk和B进行交流。

农夫希望他的奶牛可以多多沟通,所以他买了很多课本去教她的奶牛语言。当然农夫非常的吝啬,他希望买最少的书就可以让所有的奶牛可以交流。你的任务就是帮他算出最少需要买几本书。

输入格式

* Line 1: Two space-separated integers: N and M

第二行到第N+1行,每一行第一个数 K i K_i Ki代表会的语言个数,而后K个是语言编号

输出格式

算出最少需要买几本书。

样例 #1

样例输入 #1

3 3 
2 3 2 
1 2 
1 1

样例输出 #1

1

题解/启示

看到此类有间接字眼的问题,第一时间想到并查集,这题的本质就是小的并查集要并到大的并查集,要注意的就是注意统计问题。

AC代码

#include <iostream>
#include <algorithm>
#include <fstream>

using namespace std;
typedef int ll;
const ll maxn = 3e4 + 10;
ll fa[maxn], wt[maxn], k2[maxn], cnt[maxn]; // wt代表高度,cnt统计语言出现次数,也标志各语言是否出现(是否非0),
                                            //并不是m个语言都出现,k2是tmp数组
ll n, m, res;
ll find(ll x)
{
    if (fa[x] == x)
        return x;
    return fa[x] = find(fa[x]);
}
void unite(ll x1, ll x2)
{
    ll fa1 = find(x1);
    ll fa2 = find(x2);
    if (fa1 == fa2)
        return;
    if (wt[fa1] > wt[fa2])
    {
        fa[fa2] = fa1;
    }
    else
    {
        fa[fa1] = fa2;
    }
    if (wt[fa1] == wt[fa2])
        wt[fa2]++;
}
int main()
{
    //一开始文件名有非法符号_,故不能通过,有中文“桌面”也不行,估计是编译器或编辑器的编码问题
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        ll k;
        cin >> k;
        ll tmp;
        cin >> tmp;
        if (!find(tmp))//若未初始化,即代表该语言第一次出现,进行初始化
        {
            fa[tmp] = tmp;
            wt[tmp] = 1;
            cnt[tmp]++;
        }
        if (k == 1)
            continue;
        for (int j = 2; j <= k; j++)
        {
            cin >> k2[j];
            if (!find(k2[j]))//若未初始化,即代表该语言第一次出现,进行初始化
            {
                wt[k2[j]] = 1;
                fa[k2[j]] = k2[j];
                cnt[k2[j]]++;
            }
            unite(k2[j], tmp);
        }
    }
    ll max1 = -1;
    ll maxidx = -1;
    for (int i = 1; i <= m; i++)
    {
        if (find(i) == 0) //错因1:不是每个语言都被学了,一开始没注意
        {
            continue;
        }
        ll tmp = ++cnt[find(i)];//统计语言出现次数,后续补要从次数小的并查集往大的并查集里靠,很有必要
        if (max1 < tmp)
        {
            max1 = tmp;
            maxidx = fa[i];
        }
    }
    for (int i = 1; i <= m; i++)
    {
        if (cnt[i] >= 1 && find(i) != maxidx) //错因2:一开始使用fa[i] != maxidx,未能及时更新fa[i]
        {
            unite(i, maxidx);
            res++;
        }
    }
    cout << res << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值