LuoguP1196 [NOI2002]银河英雄传说 并查集

传送门

题意:每次把一个序列接到另一个序列的末尾,求某次操作之后两元素的距离。

题解:集合问题一般采用并查集。注意到每个集合必定是一条链,于是对每个集合,维护链头(即集合的根节点),链尾,每个节点到其父节点的距离。查询操作同带权并查集,合并操作就把一条链的链头接到另一条链的链尾。
需要注意的是,路径压缩的过程中,先记录该节点原来的父节点,递归完成后再用父节点的dis值更新该节点的dis值。否则会用根节点的dis值(0)更新该节点,造成错误。

看了其他的题解发现可以直接记录该集合的size值来代替链尾的记录

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 30005;

int fa[MAXN], dis[MAXN], tail[MAXN];

int get(int t){
	if(fa[t] != t){
		int f = fa[t];
		fa[t] = get(fa[t]);
		dis[t] += dis[f];
//		tail[fa[t]] = t;
	}
	return fa[t];
}

void merge(int a, int b){
	int A = get(a), B = get(b);
	if(A != B){
		fa[A] = tail[B];
		dis[A] = 1;
		tail[B] = tail[A];
	}
}

int found(int a, int b){
	int A = get(a), B = get(b);
	if(A != B) return -1;
	int ret = dis[a] - dis[b];
	if(ret < 0) ret = -ret;
	ret -= 1;
	return ret;
}

int main(){
//	freopen("in.txt", "r", stdin);
	for(int i = 1; i <= 30000; i++) fa[i] = i, tail[i] = i;
	int T;
	scanf("%d", &T);
	for(int i = 1; i <= T; i++){
		char ch; int a, b;
		scanf("
%c%d%d", &ch, &a, &b);
		if(ch == 'M'){
			merge(a, b);
		}
		else{
			printf("%d
", found(a, b));
		}
	}
	
	return 0;
}
原文地址:https://www.cnblogs.com/asdf1229/p/13892120.html