POJ 1182 食物链 【带权并查集 (种类并查集)】

POJ 1182 食物链

比较经典的带权并查集,能让你真正理解带权并查集:

诠释是这样的:在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系。也就是说,权值代表着当前节点与父节点的某种关系(即使路径压缩了也是这样),通过两者关系,也可以将同一棵树下两个节点的关系表示出来。

具体思路:
话不多说,我不会,上别人的优质题解:
https://www.luogu.com.cn/problemnew/solution/P2024
https://blog.csdn.net/c0de4fun/article/details/7318642/
https://blog.csdn.net/weixin_44758733/article/details/104220585
推荐第三个链接,非常详细的题解

看题解之前注意两点:
带权并查集相对与初始并查集的区别是增加了对一个权值的维护,
即除了fa[]外,增加一个数组ral[]维护儿子与父亲的关系
同样的ral也有find和unite两个操作,故现在要解决两个操作中的ral更新维护问题,该链接以枚举的方式得出了结论

看完如果还不懂可以看看下面我对于该题解的理解

1.已知儿子与父亲的关系,以及父亲与爷爷的关系,怎么求儿子与爷爷的关系
(即找到某个元素与该集合祖先的关系,find操作所要解决的)
2.已知儿子A与父亲A(祖先A)、儿子B与父亲B(祖先B),如何求父亲B和父亲A的关系
(即将两个元素的集合合并时,两个元素所属集合祖先之间的关系,unite操作要解决的)

枚举得到的结论:(可看第三个链接)
设0表示和父亲同类,1表示是被父亲吃,2表示吃父亲
find中的结论:
(儿子相对父亲的关系+父亲相对爷爷的关系)%3=儿子相对爷爷的关系
unite中结论:
3-儿子对父亲的关系=父亲对儿子的关系
假设A->B,C->D,AC是儿子,BD是祖先,即ral[a]=b,ral[c]=d
那么求D->B,可以先求D->C=3-ral[c],则根据C->A=d-1,可得D->B,即解决了unite操作的问题

具体代码:

#include 
using namespace std;

const int N = 5e4 + 5;
int fa[N], ral[N];

void init(int n)
{
     
	for (int i = 1; i <= n; i++)
	{
     
		fa[i] = i;
		ral[i] = 0;
	}
}
int find(int son)
{
     
	if (son == fa[son])
		return son;
	int root = fa[son];
	fa[son] = find(fa[son]);
	ral[son] = (ral[son] + ral[root]) % 3;
	return fa[son];
}
void Unite(int x, int y, int d)
{
     
	int fx = find(x);
	int fy = find(y);
	fa[fy] = fx;
	ral[fy] = (3 - ral[y] + d - 1 + ral[x]) % 3;
}

int main()
{
     
	int n, k;
	scanf("%d%d", &n, &k);
	init(n);
	int type, x, y;
	int ans = 0;
	while (k--)
	{
     
		scanf("%d%d%d", &type, &x, &y);
		if (x > n || y > n) ans++;
		else if (2 == type && x == y) ans++;
		else
		{
     
			if (find(x) == find(y))
			{
     
				if (1 == type && ral[x] != ral[y]) ans++;
				if (2 == type && (ral[x] + 1) % 3 != ral[y]) ans++;
			}
			else
				Unite(x, y, type);
		}
	}
	printf("%d\n", ans);
	return 0;
}

你可能感兴趣的:(并查集,OJ题解)