前段时间做了不少并查集的题目,前天又接触了一种新的类型——分组类型的并查集,通常解法就是加一个偏移量,查了很多资料看了很多的并查集相关的解释终于把向量偏移给搞明白了,不得不说这个“向量偏移的名字”起的真是经典,完全就是借用了向量的思维。一般的分组并查集分组都不是很多(假设为n组),这个时候通常有两中不同的解法:1.开n个并查集,合并一类节点时根据分组信息合并两次,查找的时候也多查一次。2、加个偏移向量数组,在合并和查找时记得要及时的更新偏移量数组。当然第二种方法就高效了很多。
本题正是利用了第二种方法来解,由于只有两组,所以偏移向量只要记录0或者1就可以了,当然这里如果不取余直接记录真实的偏移量也是可以的,但在最后查找判断的时候也少不了取余判断这一步。查找的时候更新偏移量是很好理解的,直接就是向量加然后取余,a->fa(a的根节点时fa),那么在查找的时候,要把a直接连接在fa的父节点上的,这个时候根据向量关系就可以得到offset[a] = offset[a] + offset[fa],这里offset[a]表示从a到其根节点的偏移量,所以在这里查找的之前要先把a的根节点保存下来,因为路径压缩的时候其根节点就变成其新的根节点了。当然因为本题只有两组,所以加个取余就可以了。然后就是合并的时候的更新问题,在合并的的时候本题就是两组即要合并的两点属于不同的组,即如果a在offset为0的组那么b就在offset为1的组,反之亦然,所以他们的偏移量差就是1,那么在合并的时候就要利用向量运算来合并了,假设
a->fa,b->fb(a的根节点是fa,b的根节点是fb),现在要合并a、b,我们再假设把a的父节点合并到b的父节点上也就是pre[fa] = fb;
根据向量运算fa->fb = fa->a + a->b +b->fb,这样就能推导出偏移更新式(2-offset[fa] + 1 + offset[b])%2,化简一下就是(1+offset[b]-offset[a])%2,ok了,这样就解决掉了。
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 28311 | Accepted: 8619 |
Description
Input
Output
Sample Input
1 5 5 A 1 2 D 1 2 A 1 2 D 2 4 A 1 4
Sample Output
Not sure yet. In different gangs. In the same gang.
Source
#include <cstdio> #include <cstdlib> const int MAX = 100002; int pre[MAX],offset[MAX]; void init(int n){ int i; for(i=1;i<=n;++i){ pre[i] = i; offset[i] = 0; } } int root(int x){ int fx; if(x!=pre[x]){ fx = pre[x]; pre[x] = root(pre[x]); offset[x] = (offset[x] + offset[fx])%2; } return pre[x]; } void merge(int x,int y){ int fx = root(x); int fy = root(y); if(fx!=fy){ pre[fx] = fy; offset[fx] = (1+offset[y]-offset[x])%2; } } int main(){ //freopen("in.txt","r",stdin); int t,n,m,ra,rb; int fa,fb; char com; scanf("%d",&t); while(t--){ scanf("%d %d%*c",&n,&m); init(n); while(m--){ scanf("%c %d %d%*c",&com,&ra,&rb); if(com=='D'){ merge(ra,rb); }else{ fa = root(ra); fb = root(rb); if(fa != fb){ printf("Not sure yet.\n"); continue; } if(offset[ra] != offset[rb]){ printf("In different gangs.\n"); continue; } printf("In the same gang.\n"); } } } return 0; }