2021.02.02刷题总结

A - 一张桌子到底坐了几个人!

544的100大寿马上就要来临了,现在他要举办一场宴席,邀请了各路来宾前来祝寿。无奈的是,544的退休金并不是很多,因此需要尽可能使得宴席的桌数少。
但是544的朋友们也很挑剔,他们不会跟陌生人坐在一起,但是却可以跟朋友的朋友坐在一起。
具体来说,如果A认识B,B认识C,C认识D,E认识F,那么A,B,C,D可以坐在一起,E和F可以坐在一起,这样安排的桌数是2。显然,E或F不能与A,B,C,D坐在一起。
由于544已经100岁了,脑袋不足以支持太过复杂的运算,所以他邀请了你来解决这个问题:给出544的朋友之间的认识关系,请问最少需要摆多少张桌子呢?
Input
输入以一个整数T开始 (1<=T<=25) 表示测试数据组数. 接下来是T个测试数据。 每个测试数据以两个整数N和M开始(1<=N,M<=1000). N表示朋友的数目,编号为1~N。接下来是M行,每行包含两个整数A和B(A!=B),表示A和B互相是朋友。每两组测试数据间以一个空行相隔。
Output
对于每组测试数据,输出一个整数,代表最少需要摆放的桌数。
Sample Input
2
5 3
1 2
2 3
4 5
5 1
2 5
Sample Output
2
4

这个题考察我们对并查集的基本掌握,刚开始我们有N个桌子,随着后面数据的输入,每合并两个桌子,N=N-1就好了。
AC代码

#include <bits/stdc++.h>
using namespace std;
int table[1100];
int find(int x)
{
    while (x != table[x])
        x = table[x] = table[table[x]];
    return x;
}
int main(void)
{
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            table[i] = i;
        int a, b, roota, rootb;
        while (m--)
        {
            cin >> a >> b;
            roota = find(a), rootb = find(b);
            if (roota != rootb)
            {
                table[roota] = rootb;
                n--;
            }
        }
        cout << n << endl;
    }
    return 0;
}

C - 撸猫到底会不会上瘾!

不知从何时起,猫(Cat)已经开始成为一类校园新型毒品. 无数少女少男深受其害,一天不吸,浑身难受. 而就在最近,这种生物竟开始携带一种传染性极强的流行性病毒 —— 喵病毒 (Meow Viruses). 凡是接触猫的人,都极有可能感染喵病毒. 而我们一般称那些感染喵病毒的人,犯了喵病. 喵病的发病症状十分邪魔. 初期为连续性地疯狂撸猫,晚期甚至半夜爬上房顶学猫叫! 而由于喵病毒传染性极强,它已逐渐被认为是一种全球性的威胁. 为了减少传播给别人的机会, 最好的策略就是隔离可能的患者.
在Mr.蒟蒻的大学中,有许多学生团体. 同一个团体的学生经常彼此相通,一个学生可以同时加入几个团体. 为了防止喵病毒的传播,学校收集了所有学生团体的成员名单. 应对措施如下:
一旦一个团体中有一个患者,该团内的所有的成员就都可能是患者. 为了遏制这种病毒的传播,我们需要找到所有可能的患者. 现在已知编号为0的孟同学(感染源)已经犯了喵病,请你设计程序,找出所有可能的患者!
Input
输入文件包含多组数据,对于每组测试数据:
第一行依次为两个整数N和M, 其中N是学生的数量, M是学生团体的数量.
0 < N <=30000,0 <= M <= 500。
每个学生编号是一个0到N - 1之间的整数,一开始只有0号的孟同学被视为患者.
紧随的每一行是每一个团体的成员列表. 每行有一个整数K,代表成员数量. 之后有K个整数代表这个团体的学生. 一行中的所有整数由至少一个空格隔开. N = M = 0表示输入结束,不需要处理.
Output
对于每组测试数据, 一行输出一个正整数,即可能的患者数量。
Sample Input
100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0
Sample Output
4
1
1
Hint
并查集

这题也是考察对并查集的基本掌握情况,将集合合并,然后统计0所在集合的元素数量就行了

#include<iostream>
using namespace std;
const int maxn = 30100;
int relative[maxn]; //节点之间的附属关系
int height[maxn];   //节点树的高度
int n, m;
void init()
{
    for (int i = 0; i < n; i++)
        height[i] = 1, relative[i] = i;
}
int getroot(int x)
{
    while (x != relative[x])
        x = relative[x] = relative[relative[x]];
    return x;
}
void unionset(int a, int b)
{
    int roota = getroot(a), rootb = getroot(b);
    if (roota == rootb)
        return;
    if (height[roota] < height[rootb]) //把低的树连接到高的树上,能够降低合并后的高度,提高查找速度
        relative[roota] = rootb;
    else
    {
        if (height[rootb] == height[roota]) //如果b树和a树高度相等,那么b树连接到a树的时候,a树的高度要+1
            height[roota]++;
        relative[rootb] = roota; //把b树连接到a树上面
    }
}
int main(void)
{
    while ((cin >> n >> m))
    {
        if (n == 0 && m == 0)
            break;
        init();
        int t, a, b;
        int ans = 1;
        while (m--)
        {
            cin >> t;
            cin >> a;
            for (int i = 2; i <= t; i++)
                cin >> b, unionset(a, b);//合并a树和b树
        }
        int root = getroot(0);
        for (int i = 1; i < n; i++)
        {
            //cout << i << " : " << getroot(i) << endl;
            ans += getroot(i) == root;
        }
        cout << ans << endl;
    }
    return 0;
}

这里合并的时候用到了树高的判断,尽量防止树的退化。

B - 宝可梦到底有多少钉子

代学长的宝可梦牧场中只有三种属性的宝可梦,水、火、草。这三种属性互相克制。(水克火,火克草,草克水)
现有N个宝可梦,以1-N编号,但是代学长并不知道每个宝可梦是什么属性。
孟学长调查了牧场之后,用两种说法对这N个宝可梦的属性进行描述:
第一种说法是"1 X Y",表示X和Y是同属性。
第二种说法是"2 X Y",表示X克Y。
孟学长对N个宝可梦,用上述两种说法,一句接一句地说出K句话,但是孟学长经常犯错,K句话有对有错。当一句话满足下列三条之一时,这句话就是错的,否则就是对的。
1) 当前的话与前面的某些对的话冲突,就是错的;
2) 当前的话中X或Y比N大,就是错的;
3) 当前的话表示X克X,就是错的。
孟学长每犯错一次就会收获一枚钉子,请根据给定的N(1 <= N <= 50,000)和K(0 <= K <= 100,000)句话,算出孟学长收获钉子的数量。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是属性。
若D=2,则表示X克Y。
Output
只有一个整数,表示钉子的数目。
Sample Input
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
Sample Output
3

这题相对于前两题,难度加大了,按照并查集的特点,我们只知道两个元素之间是不是在同一个集合内,并不能判断他们之间的其他关系。
这题我在《挑战程序设计竞赛》里面看到过原题,书上的题解很奇妙,下面是我理解书上意思后的思路:
假设我们有N个宠物,我们就创建三个大小为N的类别A、B、C

他们之间的关系是:A吃B,B吃C,C吃A。

A(i)、B(i)、C(i)分别表示三个类别里的 i 。

如果 x 和 y 是同一组,我们就 合并 A(x)和A(y)、B(x)和B(y)、C(x)和C(y) ①。

如果 x 吃 y 的话,我们就 合并 A(x)和B(y)、B(x)和C(y)、C(x)和A(y) ②,
因为 A吃B,B吃C,C吃A。

x是不是能吃y
判断 A(x)和A(y) 不是同一组 (判断 B(x)和C(y) 或 C(x)和A(y) 也行),说明x和y不是同类;
并且 A(x)和C(y) 不是同一组 ,说明y不能吃x;
如果两个条件都成立,说明x可以吃y,我们就执行②操作。

x 和 y 是不是 同一组
判断 A(x)和B(x) 不是同一组,说明 x 不能吃 y。
判断 A(x)和C(x) 不是同一组,说明 y 不能吃 x。
如果两个条件都成立,说明x和y不能互相吃,所以他们是同一组,我们执行①操作。

刚开始交这道题,因为这题的输入量有点大,然后我用 cin,就使时间超限,最后我把输入输出全都改成c语言代码,就过了。
AC代码:

#include <stdio.h>
const int maxn = 50100;
int relative[3 * maxn]; //节点之间的附属关系
int height[3 * maxn];   //节点树的高度
int n, m;
#define A(x) (x)
#define B(x) (x + n)
#define C(x) (x + 2 * n)
void init(int n)
{
    for (int i = 0; i < n; i++)
        height[i] = 1, relative[i] = i;
}
int getroot(int x)
{
    while (x != relative[x])
        x = relative[x] = relative[relative[x]];
    return x;
}
void unionset(int a, int b)
{
    int roota = getroot(a), rootb = getroot(b);
    if (roota == rootb)
        return;
    if (height[roota] < height[rootb]) //把低的树连接到高的树上,能够降低合并后的高度,提高查找速度
        relative[roota] = rootb;
    else
    {
        if (height[rootb] == height[roota]) //如果b树和a树高度相等,那么b树连接到a树的时候,a树的高度要+1
            height[roota]++;
        relative[rootb] = roota; //把b树连接到a树上面
    }
}
int main(void)
{
    int n, k, ans = 0;
    scanf("%d%d", &n, &k);
    //cin >> n >> k;
    init(3 * n);
    int d, x, y;
    while (k--)
    {
        scanf("%d%d%d", &d, &x, &y);
        x--, y--;
        if ((!(x >= 0 && x < n)) || (!(y >= 0 && y < n)))
        {
            ans++;
            continue;
        }
        if (d == 1)
        {
            if (getroot(A(x)) != getroot(B(y)) && getroot(A(x)) != getroot(C(y)))
                //x不能吃y,y不能吃x
                unionset(A(x), A(y)), unionset(B(x), B(y)), unionset(C(x), C(y));
            else
                ans++;
        }
        else
        {
            if (x == y)
            {
                ans++;
                continue;
            }
            if (getroot(A(x)) != getroot(A(y)) && getroot(A(x)) != getroot(C(y)))
                //x和y不是同类,y不能吃x
                unionset(A(x), B(y)), unionset(B(x), C(y)), unionset(C(x), A(y));
            else
                ans++;
        }
    }
    printf("%d\n", ans);
    //cout << ans << endl;
    return 0;
}

END!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值