寒假打卡第一天

查并集

1.将两个集合合并
2.询问两个元素是否在一个集合当中

复杂度近乎O(1)
用树维护所有元素
基本原理:每个集合用一颗树表示。树根的编号就是整个集合的编号。每个结点储存他的父结点,p[x]表示他的父结点
问题一:如何判断树根:if(p[x]==x)
问题二:如何求x的集合编号:while(p[x]!=x)x=p[x];
问题三:如何合并两个集合:px是x的集合编号py是y的集合编号。p[x]=y
优化-路径压缩:查到祖先后就把px存成祖先

查并集----合并集合

题目描述
一共有 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≤105

输入样例

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:

Yes
No
Yes

 #include <iostream>
 #include <algorithm>
 using namespace std;
 const int N=100010;
 int n,m;
 int p[N];
 int find(int x)//返回x的祖宗节点+路径压缩
 {
   if(p[x]!=x)p[x]=find(p[x]);
   return p[x];
 }
 int main()
 {
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++)p[i]=i;
   while(m--)
   {
     char op[2];
     int a,b;
     scanf("%s%d%d",op,&a,&b);
     if(op[0]=='M')p[find(a)]=find(b);
     else
     {
       if(find(a)==find(b))puts("Yes");
       else puts("No");
     }
   }
   return 0;
 }

查并集----连通块中点的数量

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

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

“C a b”,在点a和点b之间连一条边,a和b可能相等;
“Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
“Q2 a”,询问点a所在连通块中点的数量;
输入格式
第一行输入整数n和m。

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

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

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

每个结果占一行。

数据范围
1≤n,m≤105
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3

#include <iostream>

 using namespace std;
 const int N=100010;
 int n,m;
 int p[N],siz[N];
 int find(int x)//返回x的祖宗节点+路径压缩
 {
   if(p[x]!=x)p[x]=find(p[x]);
   return p[x];
 }
 int main()
 {
   scanf("%d%d",&n,&m);
   for(int i=1;i<=n;i++)
   {
     p[i]=i;
     siz[i]=1;
   }
   while(m--)
   {
     char op[5];
     int a,b;
     scanf("%s",op);
     if(op[0]=='C')
     {
       scanf("%d%d",&a,&b);
       if(find(a)==find(b))continue;
       siz[find(b)]+=siz[find(a)];
       p[find(a)]=find(b);
     }
     else if(op[1]=='1')
     {
       scanf("%d%d",&a,&b);
       if(find(a)==find(b))puts("Yes");
       else puts("No");
     }
     else
     {
       scanf("%d",&a);
       printf("%d\n",siz[find(a)]);
     }
   }
   return 0;
 }

查并集----食物链

动物王国中有三类动物 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 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

当前的话与前面的某些真的话冲突,就是假话
当前的话中 X 或 Y 比 N 大,就是假话
当前的话表示 X 吃 X,就是假话
你的任务是根据给定的 N 和 K 句话,输出假话的总数。

输入格式
第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式
一行,一个整数,表示假话的总数。

样例输入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
样例输出
3
分析:三种关系:A吃B,B吃C,C吃A。结合查并集思想,在同一集合中,我们可以通过每个点到根结点的距离来判断关系。
A——>B——>C——>A
1.(a-b)%3=0,则a和b是同类
2.(a-b-1)%3=0.则a吃b

#include<iostream>
#include <algorithm>
using namespace std;
const int N=1e6+10;
int p[N],d[N];
int ans;
int find(int x)
{
	if(x!=p[x])
	{
		int t=find(p[x]);
		d[x]+=d[p[x]];
		p[x]=t;
	}
	return p[x];
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)p[i]=i;
	while(m--)
	{
		int l,a,b;
		scanf("%d%d%d",&l,&a,&b);
		if(a>n||b>n)ans++;
		else
		{
			int fa=find(a),fb=find(b);
			
			if(l==1)
			{
				if(fa==fb&&(d[a]-d[b])%3!=0)
					ans++;
				else if(fa!=fb)
				{
					p[fa]=fb;
					d[fa]=d[b]-d[a];
				}
			}
			else
			{
				if(fa==fb&&(d[a]-d[b]-1)%3!=0)
					ans++;
				else if(fa!=fb)
				{
					p[fa]=fb;
					d[fa]=d[b]-d[a]+1;
				}
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值