1.朋友关系
大致题意:按照如下格式处理一堆数据 1/N 2/Y 3/Y 4/N,表示的意义为 小朋友编号/与前一名小朋友是否同班,要求最后按照集合分类输出,并要求输出顺序按照集合中编号最小的小朋友的顺序排列。(小朋友的编号>=1)
需要注意数据中可能存在不合法的输入,如:1/N 2/N 1/Y,意义为2和1不同班但是1和2同班,这显然是错误的输入,此时要求直接输出ERROR。
思考过程:
一、对于集合关系很明显想到使用并查集,在遇到不同班关系时进行判定,若属于同一个集合为非法数据,否则进行合并。
欠缺:如此做法的弊端在于并查集只能维护相容的关系而没有办法维护互斥的关系,反面例子为: 1/N 2/N 1/Y,并查集只能在相容关系建立后对互斥关系进行判定,而无法在互斥关系建立后对相容关系进行判定,因为并查集并不针对互斥关系进行记录。
二、使用两个并查集分别维护同班和不同班关系,将互斥关系进行记录。
1)如果当前这个小朋友还没有和其他小朋友建立关联(无论是同班还是不同班),那么直接建立当前需要的关系即可。
2)如果当前这个小朋友已经和其他小朋友建立关联(无论是同班还是不同班),那么需要考察当前要建立的关系是否会与已经存在的关系产生冲突。
3)由于第一个输入不存在前一个,所以应该在同班关系上默认是N,但是为了谨慎起见,代码中可以加入考量以增强鲁棒性。
欠缺:该题中并无从知道有多少个集合,自然也就无法知道 互斥 + 互斥 = ?,因为两个都不与1同班的人,可能同班也可能不同班,因此如此记录互斥关系并不正确。
三、从一出发,既然没有办法在关系建立前进行互斥关系的判定,那么不妨将所有的互斥关系判定留到所有的相容关系建立后。当相容关系建立后,遇到不正确的互斥关系则代表输入有误,遇到正确的互斥关系则并不会对已经建立的相容关系产生任何影响。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MaxN = 100010;
int father[MaxN];
int Find(int x) {
return x == father[x] ? x : father[x] = Find(father[x]);
}
void Union(int A, int B) {
A = Find(A);
B = Find(B);
if (A != B) {
father[A] = B;
}
}
int main() {
int pre = -1, p, i, maxIndex = 0, j;
char relation;
vector<int> excludes;
while (scanf("%d/%c", &p, &relation) != EOF) {
if (!father[p]) {
father[p] = p;
}
if (relation == 'N' && pre != -1) { // 互斥关系先进行保存, 建图完成后进行验证
excludes.push_back(pre);
excludes.push_back(p);
}
else if (relation == 'Y' && pre != -1) {
Union(pre, p);
}
maxIndex = max(maxIndex, p);
pre = p;
}
for (i = excludes.size() - 2; i >= 0; i -= 2) {
pre = excludes[i];
p = excludes[i + 1];
if (Find(pre) == Find(p)) { //互斥关系的元素在同一个集合
cout << "ERROR";
return 0;
}
}
for (i = 0; i <= maxIndex; ++i) {
if (p = Find(father[i])) {
for (j = i; j <= maxIndex; ++j) {
if (Find(father[j]) == p) {
cout << j << " ";
father[j] = 0; // 输出后下次就不再输出,不存在0号的小朋友
}
}
cout << endl;
}
}
return 0;
}