[kuangbin带你飞]专题五 并查集 题解

版权声明:点个关注(^-^)V https://blog.csdn.net/weixin_41793113/article/details/88931306

 专题五 并查集

POJ 2236 Wireless Network

 

鸡山村发生了一次地震。ACM (Asia Cooperated Medical 亚洲联合医疗队) 已经为圣维尔供电中心的电脑搭建了一个无线网络,但受到了一次不可预知的余震攻击,因此网络中的所有电脑都被破坏了。电脑被逐台修复,网络逐步恢复了工作。由于受到硬件的约束,每台电脑只能与距离它不超过 d 米的其它电脑直接通信。但每台电脑可被看作其它两台电脑的通信中转点,也就是说,如果电脑 A 和电脑 B 可以直接通信,或存在一台电脑 C 既可与 A 也可与 B 通信,那么电脑 A 和电脑 B 之间就能够通信。 

在处理网络修复的过程中,工作人员们在任何一个时刻,可以执行两种操作:维修一台电脑,或测试两台电脑是否能够通信。请您找出全部的测试操作。 

输入

第一行包含了两个整数 N 和 d (1 <= N <= 1001, 0 <= d <= 20000)。此处 N 是电脑的数目,编号从 1 到 N;同时,D 是两台电脑之间能够直接通信的最大距离。接下来的 N 行,每行包含两个整数 xi, yi (0 <= xi, yi <= 10000),表示 N 台电脑的坐标。从第 (N+1) 行到输入结束,是逐一执行的操作,每行包含一个操作,格式是以下两者之一: 
1. "O p" (1 <= p <= N),表示维护电脑 p 。 
2. "S p q" (1 <= p, q <= N),表示测试电脑 p 和 q 是否能够通信。 

输入不超过 300000 行。 

输出

对于每个测试操作,如果两台电脑能够通信,则打印 "SUCCESS";否则,打印 "FAIL"。

示例输入

4 1
0 1
0 2
0 3
0 4
O 1
O 2
O 4
S 1 4
O 3
S 1 4

示例输出

FAIL
SUCCESS

挺简单的一道并查集,每一次修复和已修列表中的点继续一次有条件的合并,查询更简单O(1)

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

int n,d,x,y;
char op;
int f[1005];
int w[1005][2];
vector<int> v;//钻入激活过的点

int find(int x){
    if(f[x]==x)
        return x;

    return f[x] = find(f[x]);
}


void merge(int x,int y){
    int a = find(x);
    int b = find(y);
    if(a!=b)
        f[a] = b;
}


int main(){
    scanf("%d%d",&n,&d);

    for(int i=1;i<=n;i++){
        scanf("%d%d",&w[i][0],&w[i][1]);
        f[i] = i;
    }

    while(cin>>op){
        if(op=='O'){
            scanf("%d",&x);
            for(int i=0;i<v.size();i++){
                if((w[v[i]][0]-w[x][0])*(w[v[i]][0]-w[x][0])+(w[v[i]][1]-w[x][1])*(w[v[i]][1]-w[x][1])<=d*d)
                    merge(v[i],x);
            }
            v.push_back(x);
        }else if(op=='S'){
            scanf("%d%d",&x,&y);
            if(find(x)==find(y))
                printf("SUCCESS\n");
            else
                printf("FAIL\n");

        }
    }




	return 0;
}

POJ 1611 The Suspects

 

严重急性呼吸系统综合症( SARS), 一种原因不明的非典型性肺炎,从2003年3月中旬开始被认为是全球威胁。为了减少传播给别人的机会, 最好的策略是隔离可能的患者。

在Not-Spreading-Your-Sickness大学( NSYSU), 有许多学生团体。同一组的学生经常彼此相通,一个学生可以同时加入几个小组。为了防止非典的传播,NSYSU收集了所有学生团体的成员名单。他们的标准操作程序(SOP)如下:

一旦一组中有一个可能的患者, 组内的所有成员就都是可能的患者。

然而,他们发现当一个学生被确认为可能的患者后不容易识别所有可能的患者。你的工作是编写一个程序, 发现所有可能的患者。

输入

输入文件包含多组数据。

对于每组测试数据:

第一行为两个整数n和m, 其中n是学生的数量, m是团体的数量。0 < n <= 30000,0 <= m <=500。

每个学生编号是一个0到n-1之间的整数,一开始只有0号学生被视为可能的患者。

紧随其后的是团体的成员列表,每组一行。

每一行有一个整数k,代表成员数量。之后,有k个整数代表这个群体的学生。一行中的所有整数由至少一个空格隔开。

n = m = 0表示输入结束,不需要处理。

产量

对于每种情况,输出一行中的嫌疑人数量。

样本输入

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

样本输出

4
1
1

其实能想出这题用并查集,挺简单的,把每一个组合并,若一个成员同时在2个组里面,则这2个组也会自动合并,最后扫描一下0所在的祖先有多少个点

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

int n,m,k,x1,x2;
int f[30005];


int find(int x){
    if(f[x]==x)
        return x;
    return f[x] = find(f[x]);
}

void merge(int x,int y){
    int a = find(x);
    int b = find(y);
    if(a!=b)
        f[a] = b;
}





int main(){

    while(~scanf("%d%d",&n,&m),n+m){
        for(int i=0;i<=n;i++)
            f[i] = i;

        while(m--){
            scanf("%d%d",&k,&x1);
            for(int i=1;i<k;i++){
                scanf("%d",&x2);
                merge(x1,x2);
            }
        }

        int ans=0;
        for(int i=0;i<n;i++)
            if(find(i)==find(0))
                ans++;
        printf("%d\n",ans);
    }


	return 0;
}

HDU 1213 How Many Tables

 

问题描述

今天是伊格纳修斯的生日。他邀请了很多朋友。现在是晚餐时间。伊格纳修斯想知道他至少需要多少张桌子。你必须注意到并非所有的朋友都相互认识,并且所有的朋友都不想和陌生人呆在一起。

这个问题的一个重要规则是,如果我告诉你A知道B,B知道C,那意味着A,B,C彼此了解,所以他们可以留在一个表中。

例如:如果我告诉你A知道B,B知道C,D知道E,所以A,B,C可以留在一个表中,D,E必须留在另一个表中。所以Ignatius至少需要2张桌子。

输入

输入以整数T(1 <= T <= 25)开始,表示测试用例的数量。然后是T测试案例。每个测试用例以两个整数N和M开始(1 <= N,M <= 1000)。N表示朋友的数量,朋友从1到N标记。然后M行跟随。每一行由两个整数A和B(A!= B)组成,这意味着朋友A和朋友B彼此了解。两个案例之间会有一个空白行。

产量

对于每个测试用例,只输出Ignatius至少需要多少个表。不要打印任何空白。

样本输入

2

5 3

1 2

2 3

4 5

5 1

2 5

样本输出

2

4

水题一道,反向思考如果大家都不认识那不就有n个桌子了吗,每一次祖先的成功合并,答案就减一

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

int T,n,m,x,y,ans;
int f[1005];

int find(int x){
    if(f[x]==x)
        return x;
    return f[x] = find(f[x]);
}

void merge(int x,int y){
    int a = find(x);
    int b = find(y);
    if(a!=b){
        f[a] = b;
        ans--;
    }
}


int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        ans = n;
        for(int i=1;i<=n;i++)
            f[i] = i;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            merge(x,y);
        }
        printf("%d\n",ans);
    }

	return 0;
}

HDU 3038 How Many Answers Are Wrong

问题描述

TT和FF是......朋友。呃...非常非常好的朋友-________- b 

FF是一个坏孩子,他总是求助TT与他一起玩下面的游戏。这是一个非常单调乏味的游戏。首先,TT应该记下一系列整数-_- !!(无聊)。


然后,FF可以从中选择连续的子序列(例如,从第三个到第五个整数的子序列)。之后,FF将询问TT他选择的子序列的总和是什么。接下来,TT将回答FF的问题。然后,FF可以重做此过程。最后,FF必须计算整个整数序列。

无聊~~无聊~~一个非常无聊的游戏!TT根本不想玩FF。为了惩罚FF,她经常故意告诉FF错误的答案。

这个坏孩子不是傻子。FF检测到一些答案不兼容。当然,这些矛盾使得计算序列变得困难。

然而,TT是一个漂亮可爱的女孩。她对FF没有心。为了节省时间,如果确实没有逻辑错误,她保证答案是正确的。

更重要的是,如果FF找到错误的答案,他会在判断下一个答案时忽略它。

但是会有这么多问题,糟糕的FF无法确定当前的答案是对还是错。所以他决定写一个程序来帮助他解决这个问题。该计划将收到FF的一系列问题以及FF从TT收到的答案。该计划的目的是找出错误的答案数量。只有通过忽略错误的答案,FF才能计算整个整数序列。可怜的FF没有时间做这项工作。现在他正在寻求你的帮助〜(为什么要为自己麻烦~~坏男孩)

输入

第1行:两个整数,N和M(1 <= N <= 200000,1 <= M <= 40000)。意味着TT写了N个整数,FF问了M个问题。

线2..M + 1:线i + 1包含三个整数:Ai,Bi和Si。手段TT回答FF,从Ai到Bi的总和是Si。保证0 <Ai <= Bi <= N. 

您可以假设子序列的任何总和都适合32位整数。

产量

带整数的单行表示错误的答案数。

样本输入

10 5

1 10 100

7 10 28

1 3 32

4 6 41

6 6 1

样本输出

1

 

从这题开始,难度来了,链接:hdu3038 How Many Answers Are Wrong 扩展并查集

#include<iostream>
#include<cstdio>
using namespace std;
 
int n,m,ans=0;
int f[200005];
int sum[200005];
 
 
int find(int x){
    if(f[x]==x)
        return x;
    int t = f[x];
    f[x] = find(f[x]);
    sum[x]+=sum[t];
 
    return f[x];
}
 
void Union(int x,int y,int v){
    int a = find(x);
    int b = find(y);
    if(a==b){
        if(sum[x]-sum[y]!=v){//如果祖先是同一个了,就可以验算
            ans++;
            return;
        }
    }else{
        f[a] = b;
        sum[a] = -sum[x]+sum[y]+v;
    }
 
    return;
}
 
 
int main(){
    while(~scanf("%d%d",&n,&m)){//等价于scanf("%d%d",&n,&m)!=EOF
        for(int i=0;i<=n+5;i++){//每一次都是要初始化的
            f[i] = i;
            sum[i] = 0;
        }
        ans=0;
 
        while(m--){
            int a,b,v;
            scanf("%d%d%d",&a,&b,&v);
            Union(a-1,b,v);//别忘了a-1
        }
 
        printf("%d\n",ans);
    }
 
	return 0;
}

POJ 1182 食物链

Description

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这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

Source

Noi 01

poj 1182 食物链 带权并查集经典模板(NOI2001) 

#include<cstdio>
 
const int maxn = 50000+10;
 
int p[maxn]; //存父节点
int r[maxn];//存与父节点的关系 0 同一类,1被父节点吃,2吃父节点
 
void init(int n){ //初始化
    for(int x = 1; x <= n; x++){
        p[x] = x; //开始自己是自己的父亲节点
        r[x] = 0;//开始自己就是自己的父亲,每一个点均独立
    }
}
 
int find(int x){ //找父亲节点
    if(x == p[x]) return x;
 
    int t = p[x];
    p[x] = find(p[x]);
    r[x] = (r[x]+r[t])%3; //回溯由子节点与父节点的关系和父节点与根节点的关系找子节点与根节点的关系
    return p[x];
}
 
void Union(int x, int y, int d){
    int fx = find(x);
    int fy = find(y);
 
    p[fy] = fx; //合并树 注意:被 x 吃,所以以 x 的根为父
    r[fy] = (r[x]-r[y]+3+(d-1))%3; //对应更新与父节点的关系
}
 
int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    init(n);
 
    int ans = 0;
    int d, x, y;
    while(m--){
        scanf("%d%d%d", &d, &x, &y);
 
        if(x > n || y > n || (d == 2 && x == y)) ans++; //如果节点编号大于最大编号,或者自己吃自己,说谎
 
        else if(find(x) == find(y)){//如果原来有关系,也就是在同一棵树中,那么直接判断是否说谎
            if(d == 1 && r[x] != r[y]) ans++; //如果 x 和 y 不属于同一类
            if(d == 2 && (r[x]+1)%3 != r[y]) ans++; // 如果 x 没有吃 y (注意要对应Uinon(x, y)的情况,否则一路WA到死啊!!!)
        }
        else 
            Union(x, y, d); //如果开始没有关系,则建立关系
    }
    printf("%d\n", ans);
    return 0;
}

 

猜你喜欢

转载自blog.csdn.net/weixin_41793113/article/details/88931306