PC/UVa:111005/10158
爸爸的爸爸是爷爷,妈妈的妈妈是姥姥。。。。。。
一共N个人,每组输入代表将x和y设置为朋友,或者设置为敌人,或者查询是否为朋友、或者查询是否为敌人。
根据题目中提供的二元关系定义,朋友关系是等价关系:
- 朋友是自反的,自己是自己的朋友
- 朋友是对称的,朋友是相互的
- 朋友是传递的,朋友的朋友是朋友
敌人关系满足对称性和反自反性:
- 敌人是对称的,敌人是相互的
- 敌人是反自反的,没有人和自己是敌人
此外还有:
- 敌人的敌人是朋友
- 敌人的朋友是敌人
- 朋友的敌人是敌人
题意是根据输入信息,将N个人划分成若干等价类,这题属于并查集的经典题,不过之前没见过,写了好久。并查集就是通过集合的求并和查找操作,最终划分成若干等价类。一般最常用的优化方法就是在查找时进行路径压缩,至于根据深度和集合大小进行优化的合并操作,我看网上也没什么人用。
书上给了一个通过Floyd算法计算人与人之间是否有给定关系的提示,但是放到这道题中根本没法用,总不能每添加一个关系,就去跑一遍Floyd吧?即使只把受影响的部分更新一下,稍微快一些,但是UVa上依然是超时的。
网上关于这道题的并查集代码都是一模一样的,或许只有这么些才能过吧?不过我还是自己写了一份不一样的。
并查集,也叫不相交集类,用来划分等价关系。题目中只有朋友关系是等价关系,使用viFriend来表示等价类的结果,其中第i个元素表示i所属等价类的leader,当两个人的leader相同时,即属于同一等价类,初始时每个人都是自己的leader。viEnemy用来表示每个等价类的敌人,由于朋友的敌人是敌人这条规则,敌人(们)是相对于等价类而言的,所以viEnemy中只有leader对应的元素有意义,表示整个等价类的敌人是哪个等价类,初始时全部为-1,即自己和自己不是敌人。
设置两个人是朋友时,首先要保证两个人所处的等价类不能是敌人关系,然后根据这两个等价类是否已经有了敌人,分为3种情况处理:
- 都已经有了敌人,将两个朋友等价类合并,也将两个敌人等价类合并(朋友的敌人是敌人),并将被合并的等价类的敌人设置为
-1 - 只有一个有敌人,将没有敌人的等价类合并到有敌人的等价类中
- 都没有敌人,简单的将两个朋友等价类合并即可
设置两个人是敌人时,首先要保证两个人所处的等价类不能是朋友关系,然后根据这两个等价类(用xLeader和yLeader表示)是否有了敌人(用xEnemy和yEnemy表示),分成3种情况处理:
- 都已经有了敌人,即各自有各自的敌人,将
xLeader和yEnemy合并,将yLeader和xEnemy合并(敌人的敌人是朋友)。由于合并导致xLeader和yLeader不再是等价类的代表,所以其敌人应该置为-1,同时设置xEnemy和yEnemy的敌人 - 只有一个有敌人,将没有敌人的等价类和另一个敌人等价类合并
- 都没有敌人,简单的将两个等价类互相置为敌人
#include <iostream>
#include <vector>
using namespace std;
int findSet(vector<int> &viSet, const int x)
{
if (viSet[x] == x) return x;
else{
viSet[x] = findSet(viSet, viSet[x]);
return viSet[x];
}
}
void setFriend(vector<int> &viFriend, vector<int> &viEnemy, const int x, const int y)
{
int xLeader = findSet(viFriend, x);
int yLeader = findSet(viFriend, y);
int xEnemy = viEnemy[xLeader];
int yEnemy = viEnemy[yLeader];
if (xLeader == yEnemy || yLeader == xEnemy){
cout << -1 << endl;
return;
}
if(xEnemy != -1 && yEnemy != -1){
//两个集合都有敌人,合并
viFriend[yLeader] = xLeader;
viFriend[yEnemy] = xEnemy;
viEnemy[yLeader] = -1;//还原
viEnemy[yEnemy] = -1;
//没有这两行则同一集合无法合并
viEnemy[xLeader] = xEnemy;
viEnemy[xEnemy] = xLeader;
}
else if (xEnemy != -1){
//一个集合有敌人
viFriend[yLeader] = xLeader;
}
else if (yEnemy != -1){
viFriend[xLeader] = yLeader;
}
else{
//两个集合都没有敌人
viFriend[yLeader] = xLeader;
}
}
void setEnemy(vector<int> &viFriend, vector<int> &viEnemy, const int x, const int y)
{
int xLeader = findSet(viFriend, x);
int yLeader = findSet(viFriend, y);
int xEnemy = viEnemy[xLeader];
int yEnemy = viEnemy[yLeader];
if (xLeader == yLeader){
cout << -1 << endl;
return;
}
if (xEnemy != -1 && yEnemy != -1){
//两个集合都有敌人,加入到另一个的敌人中
viFriend[xLeader] = yEnemy;
viEnemy[xLeader] = -1;
viEnemy[yEnemy] = xEnemy;
viFriend[yLeader] = xEnemy;
viEnemy[yLeader] = -1;
viEnemy[xEnemy] = yEnemy;
}
else if (xEnemy != -1){
viFriend[yLeader] = xEnemy;
}
else if (yEnemy != -1){
viFriend[xLeader] = yEnemy;
}
else{
viEnemy[xLeader] = yLeader;
viEnemy[yLeader] = xLeader;
}
}
int main()
{
int N = 0;
cin >> N;
vector<int> viFriend, viEnemy;
for (int n = 0; n < N; n++)
{
viFriend.push_back(n);
viEnemy.push_back(-1);
}
int c, x, y;
while (cin >> c >> x >> y){
if (c == 0 && x == 0 && y == 0) break;
switch (c)
{
case 1:
setFriend(viFriend, viEnemy, x, y);
break;
case 2:
setEnemy(viFriend, viEnemy, x, y);
break;
case 3:
if (findSet(viFriend, x) == findSet(viFriend, y)){
cout << 1 << endl;
}
else cout << 0 << endl;
break;
case 4:
if (findSet(viFriend, x) == viEnemy[findSet(viFriend, y)] ||
findSet(viFriend, y) == viEnemy[findSet(viFriend, x)]){
cout << 1 << endl;
}
else cout << 0 << endl;
break;
default:
break;
}
}
return 0;
}
/*
10
1 0 1
1 1 2
2 0 5
3 0 2
3 8 9
4 1 5
4 1 2
4 8 9
1 8 9
1 5 2
3 5 2
0 0 0
*/
本文深入解析了一道经典的并查集算法题,探讨了如何根据输入信息将N个人划分成若干等价类,详细解释了朋友和敌人关系的处理策略,以及如何使用并查集进行高效计算。
229

被折叠的 条评论
为什么被折叠?



