查并集
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;
}