问题简述
模板题
在编程中,我们经常遇到一类问题。如给定
n
n
n 个人,
m
m
m 组关系,每组关系给你两个编号
i
,
j
i,j
i,j;表示
i
i
i 与
j
j
j 是朋友。同时如果
x
x
x 是
y
y
y 的朋友且
y
y
y 是
z
z
z 的朋友,那么
x
x
x 是
z
z
z 的朋友
最后询问
q
q
q 次,每次给
a
,
b
a,b
a,b,求
a
a
a 是否是
b
b
b 的朋友。是输出 Y
,否则输出 N
。
样例输入及输出
输入
4 5
1 2
2 4
3 2
1 3
1 4
2
1 2
1 3
输出
Y
Y
分析
判断
我们可以先讲互相间有直接朋友关系的用边连接起来。显而易见,如果两个点是联通的,那么这两个点为朋友关系,输出 Y
,否则输出 N
。
假设我们有一种方式能按先后顺序删除无用的(及所连接的两个点之前已经是联通的),那么我们将它上去。这时,我们的图变为
因为共有
n
−
1
n - 1
n−1 条边,所以显而易见图已经变为一棵树,这时我们将图用有向图表示
在树中,每个节点都有自己唯一的一个父亲,因此我们用 head[i]
来表示第
i
i
i 个点的父亲,同时根节点的父亲表示为他自己。如果题目是这样:
这时,我们要判断两个点是否想通,只需要判断两个节点的根节点相同即可。因此,我们可以写出函数 find(i)
表示
i
i
i 的根节点。具体代码如下:
int find(int x) {
//如果找到根节点(父亲是自己)
if(head[x] == x) {
return x;
}
else return find(head[x]);//找父亲
}
你也可以
将它写成:
int find(int a) {
return a == head[a]?a:head[a] = find(head[a]);
}
输出代码
scanf("%d",&c);
while(c--) {
scanf("%d%d",&d,&e);
if(find(d) == find(e)) {
printf("Y\n");
}
else{
printf("N\n");
}
}
合并
观察这张图,我们可以发现此时如果要直接合并点
4
,
5
4,5
4,5,就要把 head[5] = 4
或 head[4] = 5
。此时无论如何都要丢失之前的一部分信息。
因此,合并
d
,
e
d,e
d,e 时,我们要先找到
e
e
e 的祖先,再把 head[e 的祖先] =d
。为了方便,我们把 head[e 的祖先] =d 的祖先
,即 head[find(e)] = find(d)
。
合并部分代码
for(int i = 1;i <= b;i++) {
scanf("%d%d",&d,&e);
head[find(e)] = find(d);
}
完整代码
#include<bits/stdc++.h>
using namespace std;
int c,d,e;
int head[10005];
//int find(int a) {return a == head[a]?a:head[a] = find(head[a]);}
int find(int x) {
if(head[x] == x) {
return x;
}
else return find(head[x]);
}
int main() {
int a,b;
scanf("%d%d",&a,&b);
for(int j = 1;j <= a;j++) {
head[j] = j;
}
for(int i = 1;i <= b;i++) {
scanf("%d%d",&d,&e);
head[find(e)] = find(d);
}
scanf("%d",&c);
while(c--) {
scanf("%d%d",&d,&e);
if(find(d) == find(e)) {
printf("Y\n");
}
else{
printf("N\n");
}
}
}
优化
因为在出题时出题人可能出现构造特殊情况,如图:
这种情况(退化成链表),我们每一次查询就要运算
n
n
n 次,显然是不可取的。因此我们更新 find
程序,每次查找时将路径上说有点的父亲都变成根节点。
inline int find(int a) {return a == head[a]?a:head[a] = find(head[a]);}
Map 的用法
介绍
在 c++ 中,map
通常用来解决数组下标只能是整数的问题。在 map
中,数组下标可以是任意类型。
用法
定义一个下标用 string
表示的字符串 head
map<string,string>head;
将 abcdefg
赋值给第 abc
项:head[abc] = abcdefg
与并查集的关联
我们可以用 map
来储存并查集的每一个项,例如这道题:一中校运会之百米跑
很明显,将一大堆 string
给换成不同的 int
十分麻烦,我们可以直接基于 map
进行类似的并查集操作
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
map<string,string>head;
string find(string x) {
return head[x] == x?x:head[x] = find(head[x]);
}
int main() {
scanf("%d%d",&n,&m);
while(n--) {
string a;
cin>>a;
head[a] = a;
}
while(m--) {
string a,b;
cin>>a>>b;
if(find(a) != find(b)) {
head[find(b)] = find(a);
}
}
scanf("%d",&k);
while(k--) {
string a,b;
cin>>a>>b;
if(find(a) == find(b)) {
printf("Yes.\n");
}
else{
printf("No.\n");
}
}
}