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 <cstdio>
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章