1月4日自习总结

刷题

亲戚

亲戚 - 洛谷

题目背景

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

题目描述

规定:xx 和 yy 是亲戚,yy 和 zz 是亲戚,那么 xx 和 zz 也是亲戚。如果 xx,yy 是亲戚,那么 xx 的亲戚都是 yy 的亲戚,yy 的亲戚也都是 xx 的亲戚。

输入格式

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

以下 mm 行:每行两个数 MiMi​,MjMj​,1≤Mi, Mj≤N1≤Mi​, Mj​≤N,表示 MiMi​ 和 MjMj​ 具有亲戚关系。

接下来 pp 行:每行两个数 Pi,PjPi​,Pj​,询问 PiPi​ 和 PjPj​ 是否具有亲戚关系。

输出格式

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

输入输出样例

输入 #1

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

输出 #1

Yes
Yes
No

简简单单的并查集

思路:

这基础题还需要什么思路吗,这不就是查找、合并两个操作吗?

直接看代码

#include<stdio.h>
int connect[6000];//存编号元素的祖先
int rank[6000];//加权标记法路径压缩的实现
void inti(int n) { //初始化,让每个元素做自己的祖先
	for (int i = 1; i <= n; i++) {
		connect[i] = i;
		rank[i] = 1; //每个元素构成的树的高度为1
	}
}
int find(int n) { //找到n的祖先并返回n的祖先
	if (connect[n] == n) { //自己是自己祖先的元素就是祖先
		return connect[n];
	} else {
		int n_father = find(connect[n]); //找n祖先的祖先
		connect[n] = n_father; //n的祖先直接改为其祖先的祖先
		return connect[n];
	}
}
void unions(int a, int b) { //让a的祖先认b的祖先做祖先
	int a_father = find(a); //找到a的祖先
	int b_father = find(b); //找到b的祖先
	if (a_father == b_father) { //是同一个祖先
		return;//不需要认祖先,直接返回
	} else {
		if (rank[b] > rank[a]) { //认权多的作父
			connect[a_father] = b_father;
		} else {
			if (rank[a] == rank[b]) { //祖先同权
				rank[a]++;//给一个祖先提权
			}
			connect[b_father] = a_father; //认权作父
		}
	}
}
int main() {
	int n, m, p;
	scanf("%d %d %d", &n, &m, &p);
	inti(n);
	for (int i = 1; i <= m; i++) { //认亲戚
		int a, b;
		scanf("%d %d", &a, &b);
		unions(a, b);
	}
	for (int i = 0; i < p; i++) { //看亲戚
		int a, b;
		scanf("%d %d", &a, &b);
		if (find(a) == find(b)) { //祖先是同一人
			printf("Yes\n");
		} else {
			printf("No\n");
		}
	}
	return 0;
}

并查集

【模板】并查集 - 洛谷

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 N,MN,M ,表示共有 NN 个元素和 MM 个操作。

接下来 MM 行,每行包含三个整数 Zi,Xi,YiZi​,Xi​,Yi​ 。

当 Zi=1Zi​=1 时,将 XiXi​ 与 YiYi​ 所在的集合合并。

当 Zi=2Zi​=2 时,输出 XiXi​ 与 YiYi​ 是否在同一集合内,是的输出 Y ;否则输出 N

输出格式

对于每一个 Zi=2Zi​=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

输入输出样例

输入 #1

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

输出 #1

N
Y
N
Y

说明/提示

对于 30%30% 的数据,N≤10N≤10,M≤20M≤20。

对于 70%70% 的数据,N≤100N≤100,M≤103M≤103。

对于 100%100% 的数据,1≤N≤1041≤N≤104,1≤M≤2×1051≤M≤2×105,1≤Xi,Yi≤N1≤Xi​,Yi​≤N,Zi∈{1,2}Zi​∈{1,2}。

 思路

这不和《亲戚》那道题几乎一模一样吗?就是换成了在合并的同时中进行查找操作

代码

#include<stdio.h>
int connect[210000];//存编号元素的祖先
int rank[210000];//加权标记法路径压缩的实现
void inti(int n) { //初始化,让每个元素做自己的祖先
	for (int i = 1; i <= n; i++) {
		connect[i] = i;
		rank[i] = 1; //每个元素构成的树的高度为1
	}
}
int find(int n) { //找到n的祖先并返回n的祖先
	if (connect[n] == n) { //自己是自己祖先的元素就是祖先
		return connect[n];
	} else {
		int n_father = find(connect[n]); //找n祖先的祖先
		connect[n] = n_father; //n的祖先直接改为其祖先的祖先
		return connect[n];
	}
}
void unions(int a, int b) { //让a的祖先认b的祖先做祖先
	int a_father = find(a); //找到a的祖先
	int b_father = find(b); //找到b的祖先
	if (a_father == b_father) { //是同一个祖先
		return;//不需要认祖先,直接返回
	} else {
		if (rank[b] > rank[a]) { //认权多的作父
			connect[a_father] = b_father;
		} else {
			if (rank[a] == rank[b]) { //祖先同权
				rank[a]++;//给一个祖先提权
			}
			connect[b_father] = a_father; //认权作父
		}
	}
}
int main() {
	int n, m;
	scanf("%d %d", &n, &m);
	inti(n);
	for (int i = 1; i <= m; i++) { 
		int a, b, c;
		scanf("%d %d %d", &a, &b, &c);
		if (a == 1) {
			unions(c, b);
		} else {
			if (find(c) == find(b)) { //祖先是同一人就是在同一集合
				printf("Y\n");
			} else {
				printf("N\n");
			}
		}
	}
	return 0;
}

遍历问题

遍历问题 - 洛谷

题目描述

我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树:

所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。

输入格式

输A数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。

输出格式

输出可能的中序遍历序列的总数,结果不超过长整型数。

输入输出样例

输入 #1

abc                           
cba

输出 #1

4

说明/提示

无提示

思路

给出了前序和后序序列,要求可能的中序遍历的总数,由中序遍历左根右的特点知当一个根结点只有一个子树(该子树可为左子树或右子树)时才有两种中序序列,那么一颗二叉树的中序序列的个数就只要看该二叉树中只有一个子树的根结点的数量n,那么所求答案就是2^n,怎么判断这个根结点只有一个子树呢?这就要看先序和后序的特点了,先序是根左右,后序是左右根,遍历一个子树的结点为根左、左根或根右、右根,反过来了而已,于是可以推导出,在先序序列中找两个连续的字符,如果能在后序中找到这两个字符的倒序,那么这就是一个只有一个子树的根结点

代码

#include<stdio.h>
#include<string.h>
char premin[40], last[40]; //分别存先序遍历字符串和后序遍历字符串
int main() {
	scanf("%s",&premin);
	scanf("%s",&last);
	int lenpre=strlen(premin),lenlast=strlen(last);
	int all=1;
	for(int i=0;i<lenlast-1;i++){
		for(int j=1;j<lenpre;j++){
			if(last[i]==premin[j]&&last[i+1]==premin[j-1]){
				all*=2;
			}
		}
	}
	printf("%d",all);
	return 0;
}

自学

强联通分量Tarjan算法

强连通:若一张有向图的节点两两互相可达,则称这张图是强连通的

强联通分量:极大的强连通子图

Tarjan(塔杨)算法

1.时间戳dfn[x]:节点第一次被访问的顺序。

2.追溯值low[x]:从节点出发,所能访问到的最早时间戳

..............................................。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值