Name: P1034家族
Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处
Author: goal00001111
Date: 24-12-08 12:05
Description:
描述 Description
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式 Input Format
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式 Output Format
P行,每行一个'Yes'或'No'。表示第i个询问的答案为"具有"或"不具有"亲戚关系。
样例输入 Sample Input
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
样例输出 Sample Output
Yes
Yes
No
题目分析:
本题是并查集的典型应用。
由于数据量较大,所以我们应该使用并查集中的压缩路径和按大小求并高效算法,否则会超时。
说明:
算法思想:迭代和递归。
数据结构:并查集。
时间复杂度:O(M*logN),其中M表示M棵等价类树,N表示树的深度;
空间复杂度:O(MAX),MAX表示集合成员数量;
程序语言:分别用c++和pascal实现。
附注:关于并查集的详细内容请参考拙作《一种简单而有趣的数据结构--并查集》:
http://blog.youkuaiyun.com/goal00001111/archive/2008/12/24/3595844.aspx
c++代码:
#include<iostream>
using namespace std;
int FindFatherAndReducePath(int father[], int pos);
bool UnionBySize(int father[], int posI, int posJ);
bool SameFamily(int father[], int posI, int posJ);
int main()
{
int posI, posJ;
int n, m, p;
cin >> n >> m >> p;
int *father = new int[n+1];
for (int i=0; i<=n; i++) //所有的数组元素值均初始化为-1
father[i] = -1;
for (int i=0; i<m; i++) //等价类划分
{
cin >> posI >> posJ;
UnionBySize(father, posI, posJ);
}
for (int i=0; i<p; i++) //询问posI和posJ是否具有亲戚关系
{
cin >> posI >> posJ;
if (SameFamily(father, posI, posJ))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
delete []father;
system("pause");
return 0;
}
/*
函数名称:FindFatherAndReducePath
函数功能:查找族长并压缩路径:找到族长后,将所途经的前辈结点均指向族长
输入变量:int father[]:存储了家族树信息的整型数组
int pos : 家族成员pos的序号(即数组元素下标)
输出变量:无
返回值:int :序号pos的族长大人的序号。若pos本身是族长,则返回自身
*/
int FindFatherAndReducePath(int father[], int pos)
{
if (father[pos] < 0)
return pos;
//若自己不是族长,则找到族长后,将所途经的结点均指向族长
return father[pos] = FindFatherAndReducePath(father, father[pos]);
}
/*
函数名称:UnionBySize
函数功能:按大小求并:将成员posI和posJ合并到同一个家族
输入变量:int father[]:存储了家族树信息的整型数组
int posI, posJ : 家族成员posI和posJ的序号(即数组元素下标)
输出变量:无
返回值:bool :合并是否成功信息。如果posI和posJ是同一个族长门下,不必合并,返回false,否则合并成功返回true
*/
bool UnionBySize(int father[], int posI, int posJ)
{
//首先各自去寻找自己的族长
int fI = FindFatherAndReducePath(father, posI);
int fJ = FindFatherAndReducePath(father, posJ);
if (fI == fJ) //如果是同一个族长门下,不必合并,即合并失败
return false;
else if (father[fI] < father[fJ])
{//如果族长fI的实力比fJ强,即|fI|>|fJ|,则fI当族长,并修改father[fI]和father[fJ]
father[fI] += father[fJ];
father[fJ] = fI;
}
else //否则fJ当族长
{
father[fJ] += father[fI];
father[fI] = fJ;
}
return true;
}
/*
函数名称:SameFamily
函数功能:判断成员posI和posJ是否属于同一家族
输入变量:int father[]:存储了家族树信息的整型数组
int posI, posJ : 家族成员posI和posJ的序号(即数组元素下标)
输出变量:无
返回值:若成员posI和posJ属于同一家族,返回ture,否则返回false
*/
bool SameFamily(int father[], int posI, int posJ)
{
return FindFatherAndReducePath(father, posI) ==
FindFatherAndReducePath(father, posJ);
}
PASCAL代码:
PROGRAM P1034 (INPUT, OUTPUT);
VAR
father : ARRAY [1..5000] OF INTEGER;
n, m, p, posI, posJ, i : integer;
FUNCTION FindFatherAndReducePath(pos : integer): integer;
begin
if father[pos] < 0 then
FindFatherAndReducePath := pos
{若自己不是族长,则找到族长后,将所途经的结点均指向族长}
else
begin
father[pos] := FindFatherAndReducePath(father[pos]);
FindFatherAndReducePath := father[pos];
end; {else}
end; {FindFatherAndReducePath}
PROCEDURE UnionBySize(posI, posJ : integer);
var
fI, fJ : integer;
begin
{首先各自去寻找自己的族长}
fI := FindFatherAndReducePath(posI);
fJ := FindFatherAndReducePath(posJ);
if fI = fJ then {什么也不做}
else if father[fI] < father[fJ] then
begin {如果族长fI的实力比fJ强,即|fI|>|fJ|,则fI当族长,并修改father[fI]和father[fJ]}
father[fI] := father[fI] + father[fJ];
father[fJ] := fI;
end {if}
else {否则fJ当族长}
begin
father[fJ] := father[fI] + father[fJ];
father[fI] := fJ;
end; {else if}
end; {UnionBySize}
FUNCTION SameFamily(posI, posJ : integer): boolean;
begin
SameFamily := (FindFatherAndReducePath(posI) = FindFatherAndReducePath(posJ));
end; {SameFamily}
BEGIN {MAIN}
read(n);
read(m);
read(p);
for i:=1 to n do {所有的数组元素值均初始化为-1}
father[i] := -1;
for i:=1 to m do {等价类划分}
begin
read(posI);
read(posJ);
UnionBySize(posI, posJ);
end; {for}
for i:=1 to p do {询问posI和posJ是否具有亲戚关系}
begin
read(posI);
read(posJ);
if SameFamily(posI, posJ) then
writeln('Yes')
else
writeln('No');
end; {for}
END.
Copyright: 始发于goal00001111的专栏;允许自由转载,但必须注明作者和出处
Author: goal00001111
Date: 24-12-08 12:05
Description:
描述 Description
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。
输入格式 Input Format
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
输出格式 Output Format
P行,每行一个'Yes'或'No'。表示第i个询问的答案为"具有"或"不具有"亲戚关系。
样例输入 Sample Input
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
样例输出 Sample Output
Yes
Yes
No
题目分析:
本题是并查集的典型应用。
由于数据量较大,所以我们应该使用并查集中的压缩路径和按大小求并高效算法,否则会超时。
说明:
算法思想:迭代和递归。
数据结构:并查集。
时间复杂度:O(M*logN),其中M表示M棵等价类树,N表示树的深度;
空间复杂度:O(MAX),MAX表示集合成员数量;
程序语言:分别用c++和pascal实现。
附注:关于并查集的详细内容请参考拙作《一种简单而有趣的数据结构--并查集》:
http://blog.youkuaiyun.com/goal00001111/archive/2008/12/24/3595844.aspx
c++代码:
#include<iostream>
using namespace std;
int FindFatherAndReducePath(int father[], int pos);
bool UnionBySize(int father[], int posI, int posJ);
bool SameFamily(int father[], int posI, int posJ);
int main()
{
int posI, posJ;
int n, m, p;
cin >> n >> m >> p;
int *father = new int[n+1];
for (int i=0; i<=n; i++) //所有的数组元素值均初始化为-1
father[i] = -1;
for (int i=0; i<m; i++) //等价类划分
{
cin >> posI >> posJ;
UnionBySize(father, posI, posJ);
}
for (int i=0; i<p; i++) //询问posI和posJ是否具有亲戚关系
{
cin >> posI >> posJ;
if (SameFamily(father, posI, posJ))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
delete []father;
system("pause");
return 0;
}
/*
函数名称:FindFatherAndReducePath
函数功能:查找族长并压缩路径:找到族长后,将所途经的前辈结点均指向族长
输入变量:int father[]:存储了家族树信息的整型数组
int pos : 家族成员pos的序号(即数组元素下标)
输出变量:无
返回值:int :序号pos的族长大人的序号。若pos本身是族长,则返回自身
*/
int FindFatherAndReducePath(int father[], int pos)
{
if (father[pos] < 0)
return pos;
//若自己不是族长,则找到族长后,将所途经的结点均指向族长
return father[pos] = FindFatherAndReducePath(father, father[pos]);
}
/*
函数名称:UnionBySize
函数功能:按大小求并:将成员posI和posJ合并到同一个家族
输入变量:int father[]:存储了家族树信息的整型数组
int posI, posJ : 家族成员posI和posJ的序号(即数组元素下标)
输出变量:无
返回值:bool :合并是否成功信息。如果posI和posJ是同一个族长门下,不必合并,返回false,否则合并成功返回true
*/
bool UnionBySize(int father[], int posI, int posJ)
{
//首先各自去寻找自己的族长
int fI = FindFatherAndReducePath(father, posI);
int fJ = FindFatherAndReducePath(father, posJ);
if (fI == fJ) //如果是同一个族长门下,不必合并,即合并失败
return false;
else if (father[fI] < father[fJ])
{//如果族长fI的实力比fJ强,即|fI|>|fJ|,则fI当族长,并修改father[fI]和father[fJ]
father[fI] += father[fJ];
father[fJ] = fI;
}
else //否则fJ当族长
{
father[fJ] += father[fI];
father[fI] = fJ;
}
return true;
}
/*
函数名称:SameFamily
函数功能:判断成员posI和posJ是否属于同一家族
输入变量:int father[]:存储了家族树信息的整型数组
int posI, posJ : 家族成员posI和posJ的序号(即数组元素下标)
输出变量:无
返回值:若成员posI和posJ属于同一家族,返回ture,否则返回false
*/
bool SameFamily(int father[], int posI, int posJ)
{
return FindFatherAndReducePath(father, posI) ==
FindFatherAndReducePath(father, posJ);
}
PASCAL代码:
PROGRAM P1034 (INPUT, OUTPUT);
VAR
father : ARRAY [1..5000] OF INTEGER;
n, m, p, posI, posJ, i : integer;
FUNCTION FindFatherAndReducePath(pos : integer): integer;
begin
if father[pos] < 0 then
FindFatherAndReducePath := pos
{若自己不是族长,则找到族长后,将所途经的结点均指向族长}
else
begin
father[pos] := FindFatherAndReducePath(father[pos]);
FindFatherAndReducePath := father[pos];
end; {else}
end; {FindFatherAndReducePath}
PROCEDURE UnionBySize(posI, posJ : integer);
var
fI, fJ : integer;
begin
{首先各自去寻找自己的族长}
fI := FindFatherAndReducePath(posI);
fJ := FindFatherAndReducePath(posJ);
if fI = fJ then {什么也不做}
else if father[fI] < father[fJ] then
begin {如果族长fI的实力比fJ强,即|fI|>|fJ|,则fI当族长,并修改father[fI]和father[fJ]}
father[fI] := father[fI] + father[fJ];
father[fJ] := fI;
end {if}
else {否则fJ当族长}
begin
father[fJ] := father[fI] + father[fJ];
father[fI] := fJ;
end; {else if}
end; {UnionBySize}
FUNCTION SameFamily(posI, posJ : integer): boolean;
begin
SameFamily := (FindFatherAndReducePath(posI) = FindFatherAndReducePath(posJ));
end; {SameFamily}
BEGIN {MAIN}
read(n);
read(m);
read(p);
for i:=1 to n do {所有的数组元素值均初始化为-1}
father[i] := -1;
for i:=1 to m do {等价类划分}
begin
read(posI);
read(posJ);
UnionBySize(posI, posJ);
end; {for}
for i:=1 to p do {询问posI和posJ是否具有亲戚关系}
begin
read(posI);
read(posJ);
if SameFamily(posI, posJ) then
writeln('Yes')
else
writeln('No');
end; {for}
END.