【并查集专题】&【蓝桥杯备考训练】:网络分析、奶酪、合并集合、连通块中点的数量、格子游戏【已更新完成】

目录

1、网络分析(第十一届蓝桥杯省赛第一场C++ A组/B组)

2、奶酪(NOIP2017提高组)

3、合并集合(模板)

4、连通块中点的数量(模板)

5、格子游戏(《信息学奥赛一本通》)


1、网络分析(第十一届蓝桥杯省赛第一场C++ A组/B组)

小明正在做一个网络实验。

他设置了 n 台电脑,称为节点,用于收发和存储数据。

初始时,所有节点都是独立的,不存在任何连接。

小明可以通过网线将两个节点连接起来,连接后两个节点就可以互相通信了。

两个节点如果存在网线连接,称为相邻。

小明有时会测试当时的网络,他会在某个节点发送一条信息,信息会发送到每个相邻的节点,之后这些节点又会转发到自己相邻的节点,直到所有直接或间接相邻的节点都收到了信息。

所有发送和接收的节点都会将信息存储下来。

一条信息只存储一次。

给出小明连接和测试的过程,请计算出每个节点存储信息的大小。

输入格式

输入的第一行包含两个整数 n,m,分别表示节点数量和操作数量。

节点从 1 至 n 编号。

接下来 m 行,每行三个整数,表示一个操作。

  • 如果操作为 1 a b,表示将节点 a 和节点 b 通过网线连接起来。当 a = b 时,表示连接了一个自环,对网络没有实质影响。
  • 如果操作为 2 p t,表示在节点 p 上发送一条大小为 t 的信息。
输出格式

输出一行,包含 n 个整数,相邻整数之间用一个空格分割,依次表示进行完上述操作后节点 1 至节点 n 上存储信息的大小。

数据范围

1≤n≤10000
1≤m≤1e5
1≤t≤100

输入样例1:
4 8
1 1 2
2 1 10
2 3 5
1 4 1
2 2 2
1 1 2
1 2 4
2 2 1
输出样例1:
13 13 5 3
思路:

连接的时候创建一个新的根节点,作为祖先(通过创建新节点,可以实现在之后的dfs中不会将之前的信息加到后连接的计算机上

依次执行输入的指令,传达的信息都加到根节点上

dfs,把发送的信息全部累加到根节点上

遍历输出根节点的信息(如果对于节点i,p[i]==i则说明这是个根节点,不然说明这个根节点已经其点的子节点了

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=2e4+10,M=N<<1;

int p[N];

int h[M],e[M],ne[M],idx;

int add(int a,int b)
{
	e[idx]=b;//值存到e里(idx是e的编号) 
	ne[idx]=h[a];//d的编号的下一个指向h【a】指向的编号 
	h[a]=idx++;//h【a】指向b的编号 
}

int f[N];//存储信息 

int n,m;

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

void merge(int a,int b,int &root)
{
	a=find(a);
	b=find(b);
	if(a!=b)
	{
		p[a]=p[b]=root;
		add(root,a);
		add(root,b);
		root++;
	}
}

void dfs(int son,int father)
{
	f[son]+=f[father];
	for(int i=h[son];i!=-1;i=ne[i])
	{
		int j=e[i];
		dfs(j,son);
	}
}

int main()
{
	memset(h,-1,sizeof h);
	cin>>n>>m;
	
	for(int i=1;i<=n*2;i++)
	{
		p[i]=i;
	}
	 
	int root=n+1; 
	
	while(m--)
	{
		
		int t,a,b;
		cin>>t;
		cin>>a>>b;
		//cout<<"yes";
		//cout<<m<<endl;
		if(t==1)
		{
            merge(a,b,root);
		}
		else
		{
			a=find(a);
			f[a]+=b;
		}
		//cout<<m<<endl;
	}
	//cout<<"yes1";
	for(int i=n+1;i<root;i++)if(p[i]==i)dfs(i,0);//把每个根节点的值传递到每个计算机上
	for(int i=1;i<=n;i++)cout<<f[i]<<" ";
	return 0;
}
/*

4 8
1 1 2
2 1 10
2 3 5
1 4 1
2 2 2
1 1 2
1 2 4
2 2 1

13 13 5 3

*/

2、奶酪(NOIP2017提高组)

现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。

我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z=0,奶酪的上表面为 z=h。 

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。

如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去? 

空间内两点 P1(x1,y1,z1)、P2(x2,y2,z2)的距离公式如下:

输入格式

每个输入文件包含多组数据。  

输入文件的第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。  

接下来是 T组数据,每组数据的格式如下:

第一行包含三个正整数 n,h,和 r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。  

接下来的 n 行,每行包含三个整数 x、y、z,两个数之间以一个空格分开,表示空洞球心坐标为 (x,y,z)。

输出格式

输出文件包含 T 行,分别对应 T 组数据的答案,如果在第 i 组数据中,Jerry 能从下表面跑到上表面,则输出 Yes,如果不能,则输出 No

数据范围

1≤n≤1000
1≤h,r≤1e9
T≤20
坐标的绝对值不超过1e9

输入样例:
3 
2 4 1 
0 0 1 
0 0 3 
2 5 1 
0 0 1 
0 0 4 
2 5 2 
0 0 2 
2 0 4
输出样例:
Yes
No
Yes
思路:

多源dfs(推荐)

或者并查集

代码:

多源bfs

#include <bits/stdc++.h>
using namespace std;
int t, n, h, r, f[1010];
double x[1010], y[1010], z[1010];

inline double dist(double X1, double X2, double Y1, double Y2, double Z1, double Z2) {
    return sqrt((X1 - X2) * (X1 - X2) + (Y1 - Y2) * (Y1 - Y2) + (Z1 - Z2) * (Z1 - Z2));
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d%d", &n, &h, &r);
        for (int i = 1; i <= n; i++) scanf("%lf%lf%lf", &x[i], &y[i], &z[i]), f[i] = 0;
        queue<int> q; bool ok = 0;
        for (int i = 1; i <= n; i++) 
            if (z[i] <= r) q.push(i), f[i] = 1;
        while (q.size()) {
            int p = q.front(); q.pop();
            if (ok) break;
            if (z[p] + r >= h) {ok = 1; puts("Yes"); break;}
            for (int i = 1; i <= n; i++) {
                if (f[i]) continue;
                if (dist(x[p], x[i], y[p], y[i], z[p], z[i]) <= 2 * r) {
                    q.push(i), f[i] = 1;
                    if (z[i] + r >= h) {ok = 1; puts("Yes"); break;}
                }
            }
        }
        if (!ok) puts("No");
    }
    return 0;
}

并查集:

#include <bits/stdc++.h>
using namespace std;
int t, n, h, r;
int t1, s1[1010], t2, s2[1010], fa[1010];
pair<long long, pair<long long, long long> > a[1010];

int get(int x) {
    if (fa[x] != x) fa[x] = get(fa[x]);
    return fa[x];
}
double dist(double X1, double X2, double Y1, double Y2, double Z1, double Z2) {
    return sqrt(pow(X1 - X2, 2) + pow(Y1 - Y2, 2) + pow(Z1 - Z2, 2));
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%d%d%d", &n, &h, &r); t1 = 0, t2 = 0;
        for (int i = 1; i <= n; i++) fa[i] = i;
        for (int i = 1; i <= n; i++) {
            scanf("%lld%lld%lld", &a[i].second.first, &a[i].second.second, &a[i].first);
            if (a[i].first + r >= h) s1[++t1] = i;
            if (a[i].first - r <= 0) s2[++t2] = i;
        }
        if (t1 == 0 || t2 == 0) {puts("No"); continue;}
        for (int i = 1; i <= n; i++)
            for (int j = i + 1; j <= n; j++) {
                if (get(i) == get(j)) continue;
                double d = dist(a[i].second.first, a[j].second.first, a[i].second.second, a[j].second.second, a[i].first, a[j].first);
                if (d <= 2 * r) fa[get(i)] = get(j);
            }
        int b = 0;
        for (int i = 1; i <= t1; i++) {
            if (b) break;
            for (int j = 1; j <= t2; j++)
                if (get(s1[i]) == get(s2[j])) {puts("Yes"); b = 1; break;}
        }
        if (!b) puts("No");
    }
    return 0;
}

3、合并集合(模板)

一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

现在要进行 m 个操作,操作共有两种:

  1. M a b,将编号为 a和 b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1e5

输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
思路:

写好find函数和merge函数

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=1e5+5;

int n,m;


int p[N];

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

void merge(int a,int b)
{
	p[find(a)]=find(b);//a的祖先添加到b的祖先下面 
}

int main()
{
	cin>>n>>m;
	
	for(int i=1;i<=n;i++)p[i]=i;
	
	
	
	while(m--)
	{
		char op;
		int a,b;
		cin>>op;
		cin>>a>>b;
		if(op=='M')
		{
			merge(a,b);
		}
		else
		{
			if(find(a)==find(b))cout<<"Yes"<<endl;
			else cout<<"No"<<endl; 
		}
	}
	//Yes No
	return 0;
} 

4、连通块中点的数量(模板)

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

  1. C a b,在点 a 和点 b之间连一条边,a 和 b 可能相等;
  2. Q1 a b,询问点 a 和点 b是否在同一个连通块中,a 和 b 可能相等;
  3. Q2 a,询问点 a 所在连通块中点的数量;
输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 a和 b 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1e5

输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3

思路:

cnt数组统计连通块中点的数量(注意数量全放在祖先节点),合并函数(merge)中进行累加

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=1e5+10;

int n,m;

int p[N];
int cnt[N];

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

void merge(int a,int b)
{
	cnt[find(b)]+=cnt[find(a)];
	p[find(a)]=find(b);
}

int main()
{
	cin>>n>>m;
	
	for(int i=1;i<=n;i++)
	{
		p[i]=i;
		cnt[i]=1;
	}
	
	while(m--)
	{
		char op[5];
		cin>>op;
		if(op[0]=='C')
		{
			int a,b;
			cin>>a>>b;
			if(find(a)==find(b))continue;
			merge(a,b);
		}
		else if(op[1]=='1')
		{
			int a,b;
			cin>>a>>b;
			if(find(a)==find(b))cout<<"Yes"<<endl;
			else cout<<"No"<<endl;
		}
		else
		{
			int t;
			cin>>t;
			cout<<cnt[find(t)]<<endl;
		}
	}
	
	
	return 0;
}

5、格子游戏(《信息学奥赛一本通》)

Alice和Bob玩了一个古老的游戏:首先画一个 n×n的点阵(下图 n=3 )。

接着,他们两个轮流在相邻的点之间画上红边和蓝边:

1.png

直到围成一个封闭的圈(面积不必为 1)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了,他们的游戏实在是太长了!

他们甚至在游戏中都不知道谁赢得了游戏。

于是请你写一个程序,帮助他们计算他们是否结束了游戏?

输入格式

输入数据第一行为两个整数 n 和 m。n表示点阵的大小,m 表示一共画了 m 条线。

以后 m 行,每行首先有两个数字 (x,y),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是 D,则是向下连一条边,如果是 R 就是向右连一条边。

输入数据不会有重复的边且保证正确。

输出格式

输出一行:在第几步的时候结束。

假如 m 步之后也没有结束,则输出一行“draw”。

数据范围

1≤n≤200
1≤m≤24000

输入样例:
3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D
输出样例:
4
思路:

注意审题,"轮流在相邻的点之间画上红边和蓝边"表示不存在有孤立的线的情况

可以把每个点进行转换,变成一个独特的数,然后我们就可以把每个点存起来,如果新的两个点转化后,发现有共同祖先那就表示封圈了

代码:
#include<bits/stdc++.h>

using namespace std;

const int N=4e4+9;//因为二维转化为一维了,所以一维空间必须开N的平方级 (200*200) 
int p[N];

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

void merge(int a,int b)
{
	p[find(a)]=find(b);
}

int main()
{
	int n,m;
	
	cin>>n>>m;
	for(int i=1;i<=n*n;i++)p[i]=i;
	
	int cnt=1;
    for(int i=1;i<=m;i++)
    {
    	int a,b;
    	char c;
    	cin>>a>>b>>c;
    	
    	int p=(a-1)*n+b;//p表示原来的坐标 
    	
    	int q;//q表示另一端(向下或者向右) 
    	if(c=='D')q=a*n+b;
    	else q=(a-1)*n+b+1;
    	
    
    	//cout<<find(p)<<" "<<find(q)<<endl;
    	if(find(p)==find(q))
    	{
    		cout<<i;
    		return 0;
    	}
    	merge(p,q);
	}
	cout<<"draw";
	return 0;
}
  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值