并查集专题(亲戚,格子游戏,银河英雄传说)

序言

今天老师开始教我们并查集。我靠,贼水,虽然错的多。还TLE了五个点,不过洛谷的这题数据范围贼小 洛谷的数据:

n , m , p n , m , p , ( n , m , p ≤ 5000 n , m , p ≤ 5000 ) n,m,pn,m,p,(n,m,p \le 5000n,m,p≤5000) n,m,pn,m,p,(n,m,p5000n,m,p5000

我们的数据:

输入由两部分组成。
第一部分以 N , M N,M NM开始。 N N N为问题涉及的人的个数 ( 1 ≤ N ≤ 20000 ) (1≤N≤20000) (1N20000)。这些人的编号为 1 , 2 , 3 , … , N 1,2,3,…, N 1,2,3,,N。下面有M行 ( 1 ≤ M ≤ 1000000 ) (1≤M≤1 000 000) (1M1000000),每行有两个数 a i , b i a_i, b_i ai,bi,表示已知 a i a_i ai b i b_i bi是亲戚。
第二部分以 Q Q Q开始。以下 Q Q Q行有 Q Q Q个询问 ( 1 ≤ Q ≤ 1000000 ) (1≤Q≤1 000 000) (1Q1000000),每行为 c i , d i c_i, d_i ci,di,表示询问 c i c_i ci d i d_i di是否为亲戚。

正文

亲戚

时间限制: 1000 ms 空间限制: 262144 KB

题目描述

或许你并不知道,你的某个朋友是你的亲戚。他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。如果能得到完整的家谱,判断两个人是否亲戚应该是可行的,但如果两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,那么检验亲戚关系实非人力所能及。在这种情况下,最好的帮手就是计算机。为了将问题简化,你将得到一些亲戚关系的信息,如Marry和Tom是亲戚,Tom和Ben是亲戚,等等。从这些信息中,你可以推出Marry和Ben是亲戚。请写一个程序,对于我们的关于亲戚关系的提问,以最快的速度给出答案。

输入

输入由两部分组成。
第一部分以N,M开始。N为问题涉及的人的个数(1≤N≤20000)。这些人的编号为1,2,3,…, N。下面有M行(1≤M≤1 000 000),每行有两个数ai, bi,表示已知ai和bi是亲戚。
第二部分以Q开始。以下Q行有Q个询问(1≤Q≤1 000 000),每行为ci, di,表示询问ci和di是否为亲戚。

输出

对于每个询问ci, di,输出一行:若ci和di为亲戚,则输出“Yes”,否则输出“No”。

样例输入

10 7
2 4
5 7
1 3
8 9
1 2
5 6
2 3
3
3 4
7 10
8 9

样例输出

Yes
No
Yes

讲解

呃…一康见题就明白这是标准并查集啊,所以要打标准读入等东西
如果自学的话,我推荐这篇这篇,他讲的很好
讲回代码,我靠,我因为join用的是int导致运行错误,这个编译器好拉呀
最后还是海笑帮我改的

int rel[20001];//并查集数组
int relative(int x) {//路径压缩
	return (rel[x]!=x)?(rel[x]=relative(rel[x])):x;//这个三目可能有人康不下去,建议拆开成if
}
int check(int x ,int y) {//判断是否在一个家里
	return (relative(x)==relative(y))?1:0;
}
void join(int x,int y) {//加为亲戚
	x=relative(x),y=relative(y);
	rel[y]=x;
}

Wrong Answer Code

#include<bits/stdc++.h>
using namespace std;
int rel[20001];
int relative(int x) {
	return (rel[x]!=x)?(rel[x]=relative(rel[x])):x;
}
int check(int x ,int y) {
	return (relative(x)==relative(y))?1:0;
}
int join(int x,int y) {
	x=relative(x),y=relative(y);
	rel[y]=x;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie();
	int n,m,x,y,j;
	cin>>n>>m;
	for(int i=1; i<=n; i++) rel[i]=i;
	for(int i=1; i<=m; i++) {
		scanf("%d%d",&x,&y);
		join(x,y);
	}
	scanf("%d",&j);
	for(int i=1; i<=j; i++) {
		scanf("%d%d",&x,&y);
		if(check(x,y)) cout<<"Yes\n";
		else cout<<"No\n";
	}
}

Accepted Code

#include<bits/stdc++.h>
using namespace std;
int rel[20001];
int relative(int x) {
	return (rel[x]!=x)?(rel[x]=relative(rel[x])):x;
}
int check(int x ,int y) {
	return (relative(x)==relative(y))?1:0;
}
void join(int x,int y) {
	x=relative(x),y=relative(y);
	rel[y]=x;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie();
	int n,m,x,y,j;
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) rel[i]=i;
	for(int i=1; i<=m; i++) {
		scanf("%d%d",&x,&y);
		join(x,y);
	}
	scanf("%d",&j);
	for(int i=1; i<=j; i++) {
		scanf("%d%d",&x,&y);
		if(check(x,y)) cout<<"Yes\n";
		else cout<<"No\n";
	}
}

洛谷亲戚

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述

规定: x x x y y y 是亲戚, y y y z z z 是亲戚,那么 x x x z z z 也是亲戚。如果 x x x y y y 是亲戚,那么 x x x 的亲戚都是 y y y 的亲戚, y y y 的亲戚也都是 x x x 的亲戚。

输入格式

第一行:三个整数 n , m , p n,m,p n,m,p,( n , m , p ≤ 5000 n,m,p \le 5000 n,m,p5000),分别表示有 n n n 个人, m m m 个亲戚关系,询问 p p p 对亲戚关系。

以下 m m m 行:每行两个数 M i M_i Mi M j M_j Mj 1 ≤ M i ,   M j ≤ N 1 \le M_i,~M_j\le N 1Mi, MjN,表示 M i M_i Mi M j M_j Mj 具有亲戚关系。

接下来 p p p 行:每行两个数 P i , P j P_i,P_j Pi,Pj,询问 P i P_i Pi P j P_j Pj 是否具有亲戚关系。

输出格式

p p p 行,每行一个 YesNo。表示第 i i i 个询问的答案为“具有”或“不具有”亲戚关系。

样例

样例输入
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
样例输出
Yes
Yes
No

解释

这个和上面差不多,只是读入不一样了

Accepted Code
#include<bits/stdc++.h>
using namespace std;
int rel[20001];
int relative(int x) {
	return (rel[x]!=x)?(rel[x]=relative(rel[x])):x;
}
int check(int x ,int y) {
	return (relative(x)==relative(y))?1:0;
}
void join(int x,int y) {
	x=relative(x),y=relative(y);
	rel[y]=x;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie();
	int n,m,x,y,j;
	scanf("%d%d",&n,&m);
	scanf("%d",&j);
	for(int i=1; i<=n; i++) rel[i]=i;
	for(int i=1; i<=m; i++) {
		scanf("%d%d",&x,&y);
		join(x,y);
	}
	for(int i=1; i<=j; i++) {
		scanf("%d%d",&x,&y);
		if(check(x,y)) cout<<"Yes\n";
		else cout<<"No\n";
	}
}

格子游戏

时间限制: 1000 ms 空间限制: 262144 KB

题目描述

Alice和Bob玩了一个古老的游戏:首先画一个 n × n n \times n n×n的点阵(下图 n = 3 n = 3 n=3) 接着,他们两个轮流在相邻的点之间画上红边和蓝边:

直到围成一个封闭的圈(面积不必为 1 1 1)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了 ( n ≤ 200 ) (n\le 200) (n200),他们的游戏实在是太长了!他们甚至在游戏中都不知道谁赢得了游戏。于是请你写一个程序,帮助他们计算他们是否结束了游戏?

输入

输入数据第一行为两个整数 n n n m m m m m m表示一共画了m条线。以后m行,每行首先有两个数字(x, y),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是" D D D ",则是向下连一条边,如果是" R R R "就是向右连一条边。输入数据不会有重复的边且保证正确。

输出

输出一行:在第几步的时候结束。假如 m m m步之后也没有结束,则输出一行“ d r a w draw draw”。

样例输入

3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D

样例输出

4

代码

代码自行理解,因为下午被人机惨了洛谷,我自由发言权没了,大概是被xxsJC了
在这里插入图片描述
,写法有很多种
数组开小点,可能会爆,不要加ios,也会爆。

#include<bits/stdc++.h>
using namespace std;
const int N=40000*2+10;
int id[210][210];
int fu[N];
int n,m,k;
int get(int x,int y) {
	return id[x][y];
}
int fi(int x) {
	if(x!=fu[x]) return fu[x]=fi(fu[x]);
	return fu[x];
}
int main() {
//	ios::sync_with_stdio(false);
//	cin.tie();
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			id[i][j]=k++;//
		}
	}
	bool f=0;
	for(int i=1; i<=(n+1)*(n+1); i++) fu[i]=i;
	for(int i=1; i<=m; i++) {
		int st,en,x,y;
		char c;
		cin>>x>>y>>c;w
		st=get(x,y);
		if(c=='D')en=get(x+1,y);
		else en=get(x,y+1);
		int fst=fi(st),fen=fi(en);
		if(fst==fen) {
			cout<<i<<"\n";
			f=1;
			break;
		}
		fu[fst]=fen;
	}
	if(!f) cout<<"draw\n";
}

银河英雄传说

luogu problem

题目背景

公元 5801 5801 5801 年,地球居民迁至金牛座 α \alpha α 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历 799 799 799 年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

题目描述

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 30000 30000 30000 列,每列依次编号为 1 , 2 , … , 30000 1, 2,\ldots ,30000 1,2,,30000。之后,他把自己的战舰也依次编号为 1 , 2 , … , 30000 1, 2, \ldots , 30000 1,2,,30000,让第 i i i 号战舰处于第 i i i 列,形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M i j,含义为第 i i i 号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第 j j j 号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第 i i i 号战舰与第 j j j 号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

输入格式

第一行有一个整数 T T T 1 ≤ T ≤ 5 × 1 0 5 1 \le T \le 5 \times 10^5 1T5×105),表示总共有 T T T 条指令。

以下有 T T T 行,每行有一条指令。指令有两种格式:

  1. M i j i i i j j j 是两个整数( 1 ≤ i , j ≤ 30000 1 \le i,j \le 30000 1i,j30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第 i i i 号战舰与第 j j j 号战舰不在同一列。

  2. C i j i i i j j j 是两个整数( 1 ≤ i , j ≤ 30000 1 \le i,j \le 30000 1i,j30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

输出格式

依次对输入的每一条指令进行分析和处理:

  • 如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息。
  • 如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 i i i 号战舰与第 j j j 号战舰之间布置的战舰数目。如果第 i i i 号战舰与第 j j j 号战舰当前不在同一列上,则输出 − 1 -1 1

样例

样例输入
4
M 2 3
C 1 2
M 2 4
C 4 2
样例输出
-1
1

提示

战舰位置图:表格中阿拉伯数字表示战舰编号

思路

首先康题M i j是进行合并操作,对于合并操作如果不对其进行路径压缩,时间可能会爆掉–TLE,而且对于我们的光明OJ,它的数据后6个点贼恶心,用路径压缩后,还卡常,必须用 i o s ios ios优化输入输出,但是
路径压缩之后,无法轻松的将每个队列的元素顺序记录下来,很难进行C i j查询,所以要开两个数组:

一个储存队列的元素个数,另一个储存自身到父节点的距离,在每次合并时更新这两个数组。

AC Code(光明OJ)

#include<bits/stdc++.h>
using namespace std;
#define N 300001
int a[N],size[N],jul[N],n;
char b;
int x,y;
int xz(int x) {
	if(a[x]!=x) {
		int r=xz(a[x]);
		jul[x]+=jul[a[x]];
		a[x]=r;
	}
	return a[x];
}
int main() {
	freopen("galaxy.in","r",stdin);
	freopen("galaxy.out","w",stdout);
	for(int i=1; i<=N; i++) {
		a[i]=i;
		size[i]=1;
	}
	scanf("%d",&n);
	while(n--) {
		ios::sync_with_stdio(false);
		cin>>b>>x>>y;
		int xx=xz(x),yy=xz(y);
		if(b=='M') {
			a[xx]=yy;
			jul[xx]=size[yy];
			size[yy]+=size[xx];
		} else {
			if(xx!=yy) printf("-1\n");
			else printf("%d\n",abs(jul[x]-jul[y])-1);//这里-1是因为要不算尾部
		}
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

luogu AC Code

#include<bits/stdc++.h>
using namespace std;
#define N 100001
int a[N],size[N],jul[N]={},n;
char b;
int x,y;
int xz(int x) {
	if(a[x]!=x){
		int r=xz(a[x]);
		jul[x]+=jul[a[x]];
		a[x]=r;
	}
	return a[x];
}
void join(int x,int y){
	x=xz(x);
	y=xz(y);
	a[x]=y;
	jul[x]=size[y];
	size[y]+=size[x];
}
int main(){
	for(int i=1;i<=N;i++){
		a[i]=i;
		size[i]=1;
	}
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>b>>x>>y;
		if(b=='M'){
			join(x,y);
		}else{
			if(xz(x)!=xz(y)) cout<<"-1\n";
			else cout<<abs(jul[x]-jul[y])-1<<"\n";//这里-1是因为要不算尾部
		}
	}
	return 0;
}

两个代码最高数据点好像相差 100 m s 100ms 100ms,推荐用第一个代码但要去掉 f r e o p e n freopen freopen a n d and and f c l o s e fclose fclose

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值