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;
}