题意
这题大概意思就是有两个黑帮帮派,然后给定两个操作。
A操作:询问a,b的关系,如果a,b是同一个帮派的输出"In the same gang.",如果a,b不是同一个帮派的输出"In different gangs.",如果不确定则输出"Not sure yet."。
D操作:a,b两个人属于两个帮派。
其实这道题应该这样说更好理解,D操作代表两个人是异性,A操作就是询问两个人是否能是同性,还是异性,或者是不确定。题意很简单易懂,但是做起来里面还是小有名堂的。这道题是并查集的拓展-----叫种类并查集。
思路
种类并查集:假设1表示两个人是异性,0表示两个人是同性,然后就是并查集怎么去维护这么多人之间的关系呢。假设现在开两个数组:
- s[i]代表i号节点的父节点。
- r[i]代表i号节点和父节点之间的关系。如果r[i] = 1,则表示i号节点和父节点s[i]是异性关系,r[i] = 0表示i号节点和父节点s[i]的关系是同性关系。
- 现在我们并查集就需要维护两个东西,一个是数组s和一个数组r。
- 父节点并不知道它和子节点的关系,只有子节点知道它和父节点的关系
- 重要前提是给定的a 和 b可以确定他们不是同性
情况分析:
** 最开始初始化的时候每个人对自己都是同性,也就是r数组全是0!**
6. 给定a是雄性,b和c是雌性,然后就可以得到下面的图。最开始c和a是异性,所以r[c] = 1,a和b又是异性r[b] = 1,那么c和b就是同性。如果我们做路径压缩把c → b上那么r[c] = 0,因为c 和 b同性。
- 给定a和c是雄性,b是雌性也可以得到下面的图,同样的道理可以得到压缩后的关系。
仔细一点可以发现节点 C 路径压缩前后:
3. 压缩前r[c]表示的是节点c 和节点a 的关系。
4. 压缩后r[c]表示的是节点c 和节点b 的关系,实际上就是当前节点和根节点的值。
仔细观察可以得出结论1:r[c] = (r[c] + r[a]) = (r[c] + r[s[c]]) % 2;
合并c 到 e上的时候怎么确定根节点a 和 根节点d的关系
**结论2:r[a] = (r[c] + r[e] + 1)%2;**确定完了这两个重要结论就可以写代码了。
最后判断的时候也是挺简单的,重要的是里面的结论和思路。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5+5;
int s[maxn]; //父节点
int r[maxn]; //当前节点和父节点的关系
void clear_set()
{
for(int i = 0;i < maxn;i++){
s[i] = i;
}
memset(r,0,sizeof(r)); //最开始自己和自己同性
}
int find_set(int x)
{
if(x != s[x]){
int f = s[x];
s[x] = find_set(s[x]);
r[x] = (r[x] + r[f])%2; //路径压缩应该对r[x]的值进行更新
return s[x];
}
return x;
}
void union_set(int x,int y)
{
int fx = find_set(x);
int fy = find_set(y);
if(fx != fy){
s[fx] = fy;
r[fx] = (r[x] + r[y] + 1)%2;
}
}
int main()
{
int t,n,m;
scanf("%d",&t);
while(t--){
clear_set();
scanf("%d%d",&n,&m);
while(m--){
char str[5];
int x,y;
scanf("%s%d%d",str,&x,&y);
if(str[0] == 'D'){
union_set(x,y);
}
else{
int fx = find_set(x);
int fy = find_set(y);
if(fx == fy){
if((r[x] + r[y])%2 == 0){
printf("In the same gang.\n");
}
else{
printf("In different gangs.\n");
}
}
else{
printf("Not sure yet.\n");
}
}
}
}
return 0;
}
愿你走出半生,归来仍是少年~